Skip to content

fix(app-shell): render InspectorSelectField's placeholder, unreachable at all 45 call sites - #8494

Queued
os-justin wants to merge 4 commits into
mainfrom
claude/issue-8450-inspector-select-placeholder
Queued

fix(app-shell): render InspectorSelectField's placeholder, unreachable at all 45 call sites#8494
os-justin wants to merge 4 commits into
mainfrom
claude/issue-8450-inspector-select-placeholder

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #8450

InspectorSelectField declares placeholder = '—' and hands it to
SelectValue. It could never render. The field bridges the caller's value
through toInner(value ?? ''), so empty/undefined becomes the sentinel
__inspector_select_none__ — and Radix shows SelectValue's placeholder for
exactly two values, '' and undefined (shouldShowPlaceholder), neither of
which the bridge can produce. A controlled value matching no SelectItem
renders as nothing at all, so every empty select in the metadata-admin designer
drew a blank trigger.

Reproduced before changing anything, rendering the primitive directly:

props trigger textContent
value={undefined} ""
value="" placeholder="PICKME" ""
value="profile" (option exists) "Profile" — the lit control: the field IS rendering

What contradicts the brief

The call-site count is 45, not 61, and 0 of them pass a placeholder.
Measured on origin/main@c90395b20 with a brace-balanced JSX scan over every
.tsx in the repo: 51 JSX elements whose opening tag names InspectorSelectField, 45 outside test
files, in 15 files. The dispatch asked which call sites pass an explicit
placeholder — the answer is none: the only explicit placeholder anywhere in
the tree is "Pick one" inside _shared.select.test.tsx itself. So the sampling
question the brief posed ("which pass a placeholder / which rely on the default")
has a degenerate answer, and the useful split turned out to be a different one
(below). What was unreachable was the primitive's own '—' default, at all 45.

Which repair, and the measured cost of each

Both candidates need the identical predicate (does any option stand for "none"?), so they cost the same to write and change exactly the same set of call
sites. They differ on one thing, and it is decisive.

A — hand Radix undefined when there is no value. Works (the trigger draws
'—', and Radix even sets data-placeholder itself). But undefined is how
Radix spells uncontrolled, so the field becomes uncontrolled for exactly as
long as it is empty. Measured against the real Select, with a console.warn
spy:

  • empty → first selection (undefined"profile"): 1 warning,
    Select is changing from uncontrolled to controlled. …. That is every call
    site's first interaction.
  • value untouched, options arriving late and gaining a "" row
    (options=[][{value:''},…] with value="" throughout): 1 warning,
    same text. The async pickers do this routinely — useMetaOptions,
    datasetOptions, chartXOptions, fieldOptions all populate after mount.
  • While uncontrolled, useControllableState keeps Radix's own value
    (setUncontrolledProp), so a pick the owner declines to persist stays on
    screen. (Read from the installed source, not rendered.)

B — keep the sentinel, render the placeholder outside SelectValue. Same
measurements: 0 warnings on either transition, selected label still renders
across the empty→selected swap, ""-option round trip intact.

They are not equivalent on cost — B is strictly cheaper — so B is
implemented
. Two details B needs:

  • The placeholder cannot go inside SelectValue as children: Radix's
    SelectItemText only portals the selected label into the value node when
    !valueNodeHasChildren, so giving SelectValue children would silence the
    selected label. It is rendered as the trigger's child instead of
    SelectValue.
  • data-placeholder="" is set on the trigger in the empty state. Not decoration:
    data-[placeholder]:text-muted-foreground on the Shadcn trigger is how the
    empty state is greyed, Radix cannot derive the attribute through the sentinel,
    and SelectPrimitive.Trigger spreads caller props after its own attributes,
    so the override is legitimate rather than a fight.

The predicate is deliberately narrow — no value AND no option standing for
none. A non-empty value matching no option still renders blank; that is a stale
value, not an empty one, and calling it "—" would assert "nothing is stored"
about a field that is storing something. Filed separately as #8488.

Representative sample, chosen by measurement

The 45 call sites were bucketed mechanically (value expression resolved through
its const initializer; options resolved to its array literal):

bucket count what it is effect of this PR
A 10 options offer a "" row — empty IS a selection unchanged
B 13 can be empty, no "" row blank →
C 22 value always resolves to a non-empty key unchanged

(The static rule put 14 in B; ActionDefaultInspector.tsx:530 is a false
positive — its || 'expression' makes it never empty — and is counted in C by
hand. Four C members are "no literal fallback, empirically non-empty" rather than
provably so: DatasetDefaultInspector d.type / m.aggregate,
ObjectFieldInspector:645, ViewColumnInspector:270.)

The sample takes one site per bucket, and takes them through the real
inspectors
, not the primitive — four inspector components, five triggers:

bucket call site control before after
B FlowNodeConfigField.tsx:225 http_request "Method", config: {} "" "—"
B AppNavInspector.tsx:391 nav item whose type infers null "" "—"
B PageBlockInspector.tsx:545 object-form "Mode" "" "—"
B PageBlockInspector.tsx:545 object-form "Layout" "" "—"
A ObjectFieldInspector.tsx:911 field "Group" with a "" row "— No group —" "— No group —"
C FlowNodeInspector.tsx:401 node "Node Type" "http_request" "http_request"

The last two rows are the sample's own controls: A and C must not move, and did
not.

_shared.select.test.tsx case 3 — reason corrected, case kept

Its two assertions were right and are unchanged. Its stated reason was not the
operative one: it read the placeholder's absence as "the '' option matched",
but before this change the placeholder was absent in every state, matching
option or not — so the case could not have told a working field from a broken
one. The comment now says that, and points at the neighbouring case where the
very same props with the "" row stripped render "Pick one", which is what
makes this absence mean what it always claimed to mean. A third assertion was
added: the trigger text is the option label alone.

Would an implementation strictly worse than the bug pass?

No. A field that renders the placeholder always satisfies every positive row,
and fails these two:

  • does NOT render the placeholder when a value is selected — asserts
    trigger.textContent === "Profile", not merely that the placeholder is absent,
    so an implementation rendering both also fails.
  • does NOT render the placeholder when "" is itself an offered option
    asserts "— No group —".

Both live in the same describe as the positives and both stayed green under
the ablation below, which is the asymmetry that makes them load-bearing.

Ablation — the pin can fail, and fails for the right reason

Ablated the read site: restored the pre-fix trigger (a bare
SelectValue placeholder={placeholder} element under an unconditional
SelectTrigger) on top of the committed implementation. Direction predicted before running.

Mutation proven on disk, hashes and both grep directions with the matched line:

HEAD blob                       ccdf0f887245e15fd17e16a12752a8582f65f069
on-disk before mutation         ccdf0f887245e15fd17e16a12752a8582f65f069   (equal - tree clean)
on-disk after  mutation         91998e75aa76c3c53bf440a0f33bb5d4627832a0   (differs)

removed marker `data-placeholder={showPlaceholder`
  before: 1   297:          data-placeholder={showPlaceholder ? '' : undefined}
  after : 0   (gone)
injected marker `SelectValue placeholder={placeholder}` (as an opening-tag span)
  before: 0   (absent)
  after : 1   line 295, an opening tag: SelectValue placeholder={placeholder}

No rebuild is involved and none is owed: the pins reach the mutated module by the
relative source import from './_shared', not through a package exports entry,
so there is no dist/ copy that could serve a stale green.

Result — 7 failed | 16 passed (23), red by name:

× _shared.select.test.tsx > placeholder (objectui#8450) > renders the declared "—" default when the value is undefined
× _shared.select.test.tsx > placeholder (objectui#8450) > renders the declared "—" default for an empty-string value
× _shared.select.test.tsx > placeholder (objectui#8450) > renders the CALLER's placeholder — the prop that reached no output before
× _shared.select.test.tsx > placeholder (objectui#8450) > flips both ways as the value comes and goes
× _shared.select.test.tsx > placeholder (objectui#8450) > carries Radix's own data-placeholder flag in the empty state only
× FlowNodeInspector.declaredDefault.test.tsx > a declared defaultValue … > select: an unset key draws the placeholder, not the declared "GET"
× FlowNodeInspector.declaredDefault.test.tsx > the online writer … > a server-published `default` is derived into the field and still not rendered

and green throughout on both does NOT render … rows and all three
empty-value option rows. Restored by state, not by an exit code:
git hash-object back to ccdf0f887245e15fd17e16a12752a8582f65f069 (equal to
the HEAD blob) and git diff HEAD empty; re-run after restore 23 passed (23).
The script carried trap … EXIT INT TERM with absolute paths throughout.

FlowNodeInspector.declaredDefault.test.tsx

Two rows read '—' where they read ''. That file is objectui#6830's pin and
its subject — a declared defaultValue seeds no control — is untouched; the
queryByText('GET') === null / queryByText('POST') === null assertions carry it
and are unchanged. Only the mark for "nothing is selected" moved from blank to
the placeholder. The select half of #6830's ruled direction A is deliberately
not implemented here
— this PR shows "nothing is selected", never the declared
default.

objectui#3912

Read. No collision, no pre-emption. It is about BlockPropField's text
branch in previews/block-config.ts carrying placeholder as its only
capability and wanting pattern / validate / description — a
InspectorTextField placeholder, which renders fine (the native
placeholder attribute on an input element) and is being asked to carry validation semantics it should not.
This PR touches neither block-config.ts nor InspectorTextField, and adds no
capability to BlockPropField. The one point of contact is that the kind: 'select' block props at PageBlockInspector.tsx:545 now show when unset —
which affects nothing #3912 proposes. It is pm:on-hold.

Verification

Everything from the repo root with explicit positional paths.

  • pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build — exit 0
  • pnpm exec vitest run packages/app-shell/Test Files 650 passed (650) · Tests 6281 passed | 1 skipped (6282)
  • pnpm exec vitest run apps/console/ examples/byo-backend-console/ examples/console-starter/Test Files 90 passed (90) · Tests 1078 passed (1078) (the other three packages turbo ls --affected reports; run from the root, never --filter-ed, because a filtered examples/* run relocates process.cwd())
  • pnpm --filter @object-ui/app-shell run type-check — exit 0 (it is tsc --noEmit && tsc -p tsconfig.test.json, so the new test file is type-checked too)
  • pnpm --filter @object-ui/app-shell run lint (eslint ., package-scoped, no narrowing) — exit 0, ✖ 2925 problems (0 errors, 2925 warnings). The five react-refresh/only-export-components warnings on _shared.tsx are pre-existing and shifted, not new: the file's export list is byte-identical to c90395b20 (15 exports, same names), and the warnings sit on spliceArray / insertArray / appendArray / moveArray / uniqueId.
  • node scripts/check-changeset-presence.mjs✅ 3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/8450-inspector-select-placeholder.md.
  • node scripts/check-governed-queue-guard.mjs --test THE-4-PATHS✅ NOT GOVERNED
  • Control-byte self-scan over the four changed files: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' → no hits (exit 1), with a lit control that fired on a planted \x01.

No assertion here depends on window.innerWidth: nothing opens the Radix content
(the popper is the only viewport-sensitive part), every reading is the closed
trigger's textContent or an attribute, and no responsive class is asserted.

Changeset level

patch, for @object-ui/app-shell. InspectorSelectField is not exported
from any package index — it is internal to the metadata-admin designer, so no
published API moves. What changes is rendered output inside a shipped app
surface, which is what a patch-level fix is. minor would claim a new
capability; the placeholder prop was already declared and documented, it simply
never reached the DOM. (major is forbidden repo-wide — one fixed group.)
skip-changeset was not used: it is a phantom label here, and the gate above
names the real declaration.


⚠️ Every placeholder and JSX tag above is written as a capitalised word or with
its shape spelled out in prose, never as a literal short angle-bracket span:
the GitHub body sanitizer eats those, code fences included, and the eaten line
here would be the one carrying the ablation's injected marker. Session
attribution as durable prose, since footer blocks are rewritten on edit — this
PR was produced by the agent seat at
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…e at all 45 call sites

The field bridges a caller's "" through an internal sentinel so a "- None -"
option can exist at all (Radix `<Select.Item value="">` throws on render). That
bridge also guaranteed the value handed to Radix was never "" or undefined - the
only two values for which Radix renders `SelectValue`'s placeholder - and a
controlled value matching no `SelectItem` renders as nothing. So the declared
"-" default reached no rendered output anywhere in the metadata-admin designer,
and every empty select drew a blank trigger.

Measured on the tree, not assumed: 45 non-test call sites, 0 of which pass a
placeholder of their own, so what never rendered was the primitive's own default
at every one of them.

The trigger now renders the placeholder itself, on the narrow state that means
"nothing is selected" - no value AND no option standing for none. Where the
caller does offer a "" row, that row is a selection and its label still wins.
`data-placeholder` is set alongside it so Shadcn's
`data-[placeholder]:text-muted-foreground` still greys the empty state; Radix
cannot derive that attribute through the sentinel, and its trigger spreads
caller props after its own.

Handing Radix an `undefined` value instead was measured and rejected: it makes
the `Select` uncontrolled for exactly as long as the field is empty, so Radix
logs `Select is changing from uncontrolled to controlled` on the first selection
at every call site - and the same flip fires with the value untouched when a
late-arriving `options` list gains or loses its "" row, which the async pickers
(useMetaOptions, datasetOptions, fieldOptions) do routinely. While uncontrolled,
Radix also keeps its own value, so a pick the owner declines to persist stays on
screen.

`_shared.select.test.tsx` case 3 kept its assertions and had its stated reason
corrected: it read the placeholder's absence as the "" option winning, but before
this change the placeholder was absent in every state, so the case could not have
told a working field from a broken one. Its two "does not render" rows are now
the non-regression half - they are what fails on a field that renders the
placeholder always, an implementation strictly worse than the bug.

`FlowNodeInspector.declaredDefault.test.tsx` reads '-' where it read '' on two
rows. Its subject - that a declared `defaultValue` seeds no control - is
unchanged; only the mark for "nothing is selected" moved from blank to the
placeholder.

Refs: objectui#8450
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Conflict in FlowNodeInspector.declaredDefault.test.tsx's docblock only, and
only in its point 1: PR #8473 (objectui#8451) rewrote the BOOLEAN half of that
paragraph while this branch rewrote its SELECT half. Resolved by taking the
incoming boolean text verbatim and re-applying the select correction on top of
it - the select now draws InspectorSelectField's own em-dash placeholder rather
than a blank trigger, which is the same fact about `defaultValue` as before.

The file's select assertions merged cleanly and are unchanged by this commit.

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

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3473.5 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-BSIdi-1X.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) 15.67KB 5.75KB
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) 498.87KB 114.10KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 191.36KB 53.16KB
fields (index.js) 243.15KB 61.40KB
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) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
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.94KB
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) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.52KB 46.34KB
plugin-dashboard (index.js) 131.48KB 34.45KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.78KB 63.98KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 55.40KB 15.71KB
plugin-list (index.js) 112.74KB 27.70KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
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.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
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 (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

PM contract review — accepted, flipped to ready, auto-merge armed. Six corrections, two of them against the card I wrote and one against my own instruction. Taking them all.

B is right, and the two options were never equivalent

I offered "if they come out equivalent on cost, pick and say why". They are not: A makes the Radix Select uncontrolled while the field is empty, because undefined is how Radix spells uncontrolled and there is no other way to say controlled, nothing selected. Measured with a warn spy rather than argued:

  • empty → first selection logs Select is changing from uncontrolled to controlledevery call site's first interaction;
  • options arriving late and gaining a none row logs the same warning with the value untouched, so controlled-ness flips on data arrival alone, and useMetaOptions / datasetOptions / chartXOptions / fieldOptions all populate after mount;
  • while uncontrolled, a pick the owner declines to persist stays on screen.

B measured 0 warnings on both transitions. That is a cost comparison, not a preference.

⚠️ "61 call sites" is my error, and it is worse than a stale number

Measured 45 non-test call sites at your base and at a later main tip — so it is not a figure that decayed, it is unreproducible at any commit. I am correcting the card.

⭐ And the coda is the part I would not have caught: the repo-wide element count reads exactly 61 after your pins land, because _shared.select.test.tsx goes from 3 to 13 elements (51 + 10 = 61). A future reader grepping the merged tree and getting 61 will be counting your test file and concluding the card was right. A wrong number that becomes self-confirming is worth more than a footnote; it goes on the card.

The card's title is about a prop nobody uses

0 of 45 callers pass a placeholder at all. What was unreachable is the primitive's own em-dash default, not any caller's string. So my sampling instruction had a degenerate answer — there was no sample of placeholder-passing sites to choose. Replacing it with the A/B/C emptiness bucketing (10 unchanged / 13 blank → em-dash / 22 unchanged), hand-correcting the one false positive, and flagging four members as not provably non-empty rather than asserting them, is the right substitute and better evidence than what I asked for.

⚠️ "Case 3 becomes your ablation" was wrong

Under the ablation it stays green — it must, it is the non-regression half. The ablation is carried by the new positive rows. Case 3 is load-bearing, but against the always-placeholder caricature, where it does go red. Keeping it was right; calling it the ablation was my mistake and I am not repeating it.

⭐ You applied objectui#8474's correction against your own showpiece

"carries Radix's own data-placeholder flag in the empty state only"survives the caricature untouched. The attribute logic is independent of the text rendering.

And you say plainly that you had reasoned before measuring that it would refuse, and it does not. The load-bearing refusals turn out to be the plain positive text-equality assertions on a selected value — including a pre-existing one nobody wrote for this purpose.

That is the third independent reproduction of this shape today, and the first where a dev turned it on the case they had just written. It is now a standing rule in every brief: name the case that actually discriminates, and label the impressive one as a scope declaration if that is what it is.

Also accepted

  • PR fix(app-shell): seed the flow-node boolean control from the declared defaultValue (objectui#6830 arm A) #8473 had not landed when you branched, and it is not "a sibling file" — it landed mid-run and edited the very file this card had to touch. My warning to re-derive was right for the wrong reason, and resolving the conflict by taking their docblock verbatim and re-applying only your select correction is the right handling.
  • objectui#3912 read in full with a precise no-collision finding (its placeholder is InspectorTextField's native input attribute, which renders fine; its complaint is that placeholder is overloaded to carry validation semantics, not that it fails to render).
  • patch is right: InspectorSelectField is not exported from any barrel, so no published API moves — and the prop was already declared, typed and defaulted, it simply never reached the DOM.
  • The narrowing is declared honestly: the full app-shell and console/examples runs did not complete at the merged head and are not claimed as measured, with the product-file blob shown byte-identical at both heads. "Not measured, here is why" is the shape I asked for.

objectui#8488 (a stored value matching no option paints a blank trigger; 3 of 45 sites hand-roll a synthesized row, the other 42 do not) is correctly out of scope — an unknown stored value is not an empty one, and showing the placeholder for it would assert "nothing is stored" about a field that is storing something. Labelled and queued.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Test (shard 2/4) on this PR is NOT this PR's failure — it is red on main, and it is being fixed centrally

Standing down on this check rather than touching this PR, with the evidence.

The failing test is in a package this diff cannot reach. This PR changes packages/app-shell/src/views/metadata-admin/inspectors/_shared.tsx and its tests. The failure is:

FAIL packages/plugin-kanban/src/ObjectKanban.contractEnvelope-6839.test.tsx
  > "still reads the contract's `data` member"
AssertionError: the declared rows member must still draw both cards: expected +0 to be 2
  ❯ ObjectKanban.contractEnvelope-6839.test.tsx:134:81

It reproduces on an unrelated PR. #8521 (which touches plugin-kanban/src/KanbanImpl.tsx, not ObjectKanban.tsx) fails Test (shard 2/4) with a byte-identical assertion at the same line, in a run three hours later. Two PRs, different packages, same failure — that is a base-side red, not either PR's.

A fix is in flight: objectui#8532, dispatched. The mechanism is in the test's own docblock — the helper waits on the column header, which it explicitly documents as a mount signal rather than a rows signal, and then reads the rows synchronously. Under a loaded runner the first it in the file loses that race; the other three pass.

No re-run has been spent on this PR, because a re-run cannot fix a base-side failure and this one has a diagnosed mechanism rather than a suspected flake. This PR stays armed; it will merge once objectui#8532 lands and CI re-runs against a green base. Four other PRs (#8521, #8527, #8528, #8529) are queued behind the same red.

Nothing here needs a change to this PR.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3475.1 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-hT7mMKyD.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 ✅ pass
Per-chunk ceilings ❌ over its ceiling
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) 15.67KB 5.75KB
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) 498.87KB 114.10KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 191.36KB 53.16KB
fields (index.js) 243.24KB 61.42KB
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) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
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.94KB
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) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.46KB 63.90KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.30KB 56.63KB
plugin-kanban (index.js) 55.40KB 15.71KB
plugin-list (index.js) 112.74KB 27.70KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
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.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
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 (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

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3475.4 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-BuME_yhM.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 ✅ pass
Per-chunk ceilings ❌ over its ceiling
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) 15.67KB 5.75KB
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) 498.93KB 114.12KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 192.25KB 53.44KB
fields (index.js) 243.24KB 61.42KB
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) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
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.94KB
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) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.46KB 63.90KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.30KB 56.63KB
plugin-kanban (index.js) 55.40KB 15.71KB
plugin-list (index.js) 112.74KB 27.70KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
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.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
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 (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

@os-justin
os-justin added this pull request to the merge queue Sep 8, 2026
Any commits made after this event will not be merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InspectorSelectField's placeholder is unreachable at all 45 call sites

2 participants