Skip to content

fix(core): refuse a retired string sort ENTRY out loud instead of dropping it in silence - #9984

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-9955-array-sort-diagnostic
Sep 19, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-9955-array-sort-diagnostic

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #9955

What was wrong

packages/core/src/utils/sort-query.ts refused the retired scalar sort clause out
loud, and dropped the same clause one container deeper in complete silence. The
author's row order disappeared and the console said nothing.

Re-measured at source on this branch's base, objectui origin/main 8a7e09fa1 — the
array arm had exactly two statements per entry, continue and out.push, and no sink
call anywhere between the Array.isArray guard and the return.

Three arms, before and after

Each row is a real run of convertSortToQueryParams from
packages/core/src/utils/__tests__/sort-query.test.ts on this branch.

input $orderby BEFORE diagnostics BEFORE $orderby AFTER diagnostics AFTER
CONTROL A — scalar 'name asc' undefined 1 (scalar message) undefined 1 (scalar message, byte-identical)
SUBJECT — ['name asc'] undefined 0 undefined 1 (entry message)
CONTROL B — [{ field: 'name', order: 'desc' }] { name: 'desc' } 0 { name: 'desc' } 0
CONTROL C — [{ order: 'asc' }] undefined 0 undefined 0 — unchanged, on purpose
MIXED — [{ field: 'a', order: 'asc' }, 'b desc'] { a: 'asc' } 0 { a: 'asc' } 1 (names 'b desc')

Only the SUBJECT row moves, and it moves only in the diagnostics column.

⭐ CONTROL C is the load-bearing one. [{ order: 'asc' }] is also an entry that
contributes nothing, and its silence is declared in normalizeSortEntries' docblock:
"an entry with no usable field is SKIPPED (it names nothing to order by)". It stays
silent. That is what shows the seam was cut at typeof entry === 'string' and not at
"this entry has no field", which would have converted a designed silence into noise on
every partially-authored view.

⛔ The accepted input set is UNCHANGED — by one key and by one shape

sort: ['name asc'] still contributes no ordering. Nothing that was refused is now
accepted, and nothing that was accepted is now refused. Making the array-of-strings
work would re-admit the retired spelling one container deeper and reverse the director
ruling quoted in this file's own docblock — decision batch #77, 2026-09-07, objectui#8221,
maintainer approved, option B: "The platform has one sort spelling, the array,
everywhere."

Loud is not accepted. Pinned, not merely asserted, by
ACCEPTED INPUT SET UNCHANGED — the string entry contributes exactly nothing, which
compares five arrays against their own string-free twins on both
convertSortToQueryParams and normalizeSortEntries — and which is the one new case
that stays green under the ablation below, because it pins the non-change rather than
the change.

The two docblock sentences that look contradictory are both still true, and neither was
edited:

  • "Not a lenient alias: an unusable input (a number, an object, an array of strings)
    yields undefined rather than a guess" — governs the RETURN VALUE. Unchanged.
  • "A retired string is REFUSED OUT LOUD, not dropped in silence" — governs the
    DIAGNOSTIC. This is the one that was being violated, and the only one this branch moves.

The dedupe key — the decision, and why the line count stays bounded

A sibling reporter, and ARM-TAGGED keys in the one existing Set.

retiredKey(ARM, SPELLING) returns the arm token, a newline, then the spelling, so the
two key spaces are disjoint by construction: the arm token is one of two fixed words
and neither contains a newline, so the text before the first newline names the arm no
matter what an author wrote. A bare string prefix would only have been disjoint until
somebody authored the prefix — the spellings come out of authored JSON.

Why not reuse reportRetiredSortSpelling with the entry as its key:

  1. The sentence an author acts on differs. The scalar refusal can say "this query
    carries no $orderby" because refusing the whole value refuses the whole ordering. One
    bad entry among good ones does not: [{ field: 'a', order: 'asc' }, 'b desc'] still
    orders by a. Reusing the scalar text would tell an author their rows are unordered
    while they are ordered by everything except the key they are hunting for — worse than
    silence.
  2. On one shared raw key the first arm to fire silences the other, so a page carrying
    both spellings would print a sentence about the wrong container. Pinned by
    CONTROL A — the scalar arm is untouched, and the two messages differ.

The bound: ONE line per distinct spelling per arm, however many blocks inherit the
same bad view. That is the property the existing dedupe was built for — this sink runs
inside the query memo of every object-bound block, so a related-list derivation over an
object with N lists re-enters it N times per pass. Pinned by BOUNDED — N blocks inheriting one bad view print ONE line, per spelling, which drives 25 renders of one bad
view and asserts one line, then a second spelling and asserts two, then clears the seam
and asserts three.

The message text

console.error, unconditional, not dev-gated — the same channel and the same severity
class as the scalar sibling, for the reason its docblock already argues: this REFUSES an
authored row order, so the page renders in an order the author did not ask for. Not the
warnOnUnknownActionKeys class, which reports keys nothing was ever going to read.
Asserted against console.warn in the same body.

Both halves objectui#9031 ruled load-bearing survive in this arm too, and they are judged
rather than eyeballed: the new case
objectui#9031 — the ENTRY message prescribes metadata the installed spec ACCEPTS parses
the entries back out of the real emitted string and feeds them to SortItemSchema
from the INSTALLED @objectstack/spec, exactly as the scalar pin does. The message names
ARRAY_FORM_EXAMPLE, states order is required at publish, and states that the ascending
default is a runtime tolerance and not permission to omit the key.

One decision worth a reviewer's eye: the empty-string entry

[''] stays silent, and that is symmetry with the scalar arm rather than an exception
to the seam. sort: '' never reaches the loud scalar arm either — the !sort guard
answers first, and the pre-existing case
stays silent for values that were never the retired spelling says why in words: an empty
spelling is "the author asked for nothing", and reporting it would train readers to ignore
the message. Inverting that one container deeper would recreate exactly the inconsistency
this card is about, in the other direction.

Whitespace is NOT in the carve-out: ' ' is truthy, is loud on the scalar arm today,
and is loud as an entry too. Both legs are the control in
the '' entry keeps the scalar arm's silence — symmetry, not an exception.

Reverse verification (ablation) — measured, on disk, from the committed state

Declared direction before running: turns RED. Run through
scripts/ablation-replace.mjs, which proves the mutation reached the disk and proves the
restore:

anchor   x1 -> x0
replace  x0 -> x1
blob     ea88c7b574652c25132de1c60b8d457a346bf16b -> d046030a78eb15e235d3689f71bd65149bcac347
ok mutation landed
  -> Tests  7 failed | 32 passed (39)
ok restored: blob == HEAD (ea88c7b57465) and `git diff HEAD` is empty

The 7 that fall are exactly the new objectui#9955 cases that assert the diagnostic. The 32
that hold include every pre-existing scalar refusal — so the sink is demonstrably not dead,
and the new assertions are not vacuous.

The plugin-grid pin this change invalidates — updated in the same branch

This section was rewritten by the domain:ui#2 seat at 2026-09-19T02:04Z, under the standing clause that a dev writes a PR body once and the seat writes later corrections. It replaces a section headed 「⚠️ ONE RED」 that described a state which lasted one round; the evidence below is the seat's own and the implementer's, each attributed in place.

packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx had one case pinning
the very silence this card removes, and that pin anticipated this card by name. Its own
comment read:

⚠️ Deliberately NOT a new refusal diagnostic. objectui#8767 landed route C for the
retired STRING clause; widening that refusal to cover the array-of-strings is that
card's business, not this one's. This entry is dropped exactly as the shared sink drops
an unusable entry — silently.

objectui#9955 is that card, and it landed the widening. The implementer stopped at the face
boundary and asked rather than riding; the seat granted route A (verdict
5738396331) and expanded the declared face by that one file, on three readings taken in
that act:

  • open PR objectui#9924 modifies the same file — the module docblock, the imports,
    afterEach, and an appended section — and ⛔ does not touch this case's hunk;
  • every errorSpy assertion on that branch is one of the three pre-existing ones, and its
    appended section carries no errorSpy assertion and no array-of-strings case at all;
  • ⇒ the two edits are disjoint in both directions, so neither order breaks the other and
    the serialization the in-flight rule buys is buying nothing here — while the wait it costs
    is unbounded.

What the update asserts

expectNoOrderBy(params) stays first and unchanged — the RETURN value moved nothing,
and that is the half a later reader is likeliest to get wrong. The diagnostic half becomes
toHaveBeenCalledTimes(1) plus three assertions that discriminate the entry refusal
from the scalar one: the message must carry the entry sentence's own opening, must not
carry the scalar sentence's, and must quote the value that arrived. A bare
toHaveBeenCalled(), or a match on objectui#8221, would be satisfied just as well by the
scalar message — and the input in this case is an array.

The fence held

git diff -U0 on that file yields exactly one hunk header, @@ -188,5 +188,22 @@. The
scalar-refusal case, the imports, the afterEach and the module docblock — PR objectui#9924's
hunks — are all outside it. packages/core/src/utils/sort-query.ts, its test and the
changeset are byte-identical to the first commit.

The reading

Same command, same denominators, both sides: the composite affected set went from
Test Files 1 failed | 165 passed (166) / Tests 1 failed | 3400 passed (3401) to
Test Files 166 passed (166) / Tests 3401 passed (3401). ⭐ The equality of the totals is
the reading — it says the one failure is gone and nothing else moved. The whole
plugin-grid suite is green on its own: 143 files, 1348 tests.

An advisory is posted on PR objectui#9924 (5738398717) so its author is not surprised; it
asks nothing of that seat.

Verification

Run at commit 09d61f337, from the repo root, per the repo's canonical invocation.

  • pnpm exec vitest run packages/core/ — 158 files, 3319 tests, all passed.
  • pnpm --filter @object-ui/core run type-check — exit 0 (both legs: tsc --noEmit
    and tsc -p tsconfig.test.json), after pnpm --filter '@object-ui/core^...' build,
    without which every reading is a stale-dist artefact (TS6305).
  • Sink-aware consumers, run by name because the module's behaviour changed even though its
    exported symbols did not — deriveRelatedLists.inheritSort,
    RecordDetailView.relatedListInheritedSort-5795, serverSorting,
    ObjectView.sortSink, ObjectView.namedViewSortArity, gridRetiredStringSort-8767,
    ObjectCalendar.sortMembersReachTheWire-8171,
    ObjectKanban.structuredMembersReachTheirSinks-8313,
    kanban-calendar-filter-sort-8174, ListView — all passed.
  • pnpm exec eslint . --no-inline-config --format json — the whole repo, not a
    narrowing: 5151 files linted, 95 errors and 13387 warnings, every one of them
    pre-existing
    ; both files this branch touches report 0 errors and 0 warnings. The
    population and the file count are read from that JSON, not estimated.
  • pnpm check:control-bytes — OK, 8050 tracked text files scanned.
  • pnpm check:new-line-citations — 0 new citations, exit 0.
  • node scripts/check-changeset-presence.mjs — 2 published source files changed,
    1 changeset declared, exit 0.

The one red is the out-of-face pin described above, and only that.

Scope

⛔ Three things this branch deliberately does not touch, each declared out of scope by the
dispatch order:

  1. The field-less OBJECT entry — its silence is declared in the docblock and is pinned
    as CONTROL C to keep it that way.
  2. The empty array — objectui#9607, already closed as a measured no-op. Not folded in.
  3. The object-grid wire shape — route B on objectui#8767, declined by the maintainer by
    name on 2026-09-10. Nowhere near this branch.

Acceptance notes

  • noted, not filed — the dedupe Set is now keyed by ARM + newline + SPELLING while the
    sibling sink reportRetiredFieldType (same module family, same "retired spelling" idea)
    still keys on a raw value. Not a defect and not filed: that sink has one arm, so it has
    nothing to collide with. Successor that would meet it: whoever gives that sink a second
    arm. Named here so the next reader of the two does not read the asymmetry as an
    oversight.

This PR is a DRAFT and stays one: landing is the claiming seat's act after review.

🤖 Generated with Claude Code

https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq


Generated by Claude Code


Generated by Claude Code

…ropping it in silence

`normalizeSortEntries` refused the retired scalar clause (`sort: 'name asc'`)
with a full prescription, while the same clause one container deeper —
`sort: ['name asc']` — fell through the array arm's skip, returned `undefined`,
and printed nothing. An authored row order disappeared with no trace in the
console, which is the exact failure the module's own docblock argues against.

A string entry now gets its own `console.error`, deduped per spelling like its
scalar sibling. The dedupe keys are arm-tagged so the two arms cannot silence
each other; the bound each keeps is one line per distinct spelling per arm,
however many blocks inherit the same bad view.

No input starts being accepted. `['name asc']` still contributes no ordering,
per the director ruling that retired the spelling: one `sort` spelling, the
array, everywhere. Mixed arrays keep lowering their good entries, and the
message says so rather than claiming the query lost its ordering. The
`field`-less object entry keeps the silence its docblock declares, and `''`
keeps the silence the scalar `!sort` guard already gives it.

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

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

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

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

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

.changeset/8221-retire-legacy-string-sort.md

  • names sort-query.ts → packages/core/src/utils/sort-query.ts — edited by this change

    Director ruling, decision batch Release v0.2.1 with changeset automation #77 (2026-09-07), option B. Three faces disagreed about one key: @object-ui/core implemented the string clause on purpose (sort-query.ts, docblock and all), content/docs/plugins/plugin-map.mdx taught it as sort?: string | SortConfig[], and the html tier answered type-mismatch for it because all seven sort registrations publish type: 'array' alone — while @objectstack/spec refuses the string outright on element-record-picker. Option A (per-block string arms) was rejected by name: it would make one key mean different things on different blocks.

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

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

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

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3054.0 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-BQuai_wy.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) 545.91KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
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.83KB 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) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 254.30KB 66.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 137.49KB 34.64KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 113.42KB 27.95KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.05KB 21.01KB
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) 108.85KB 36.01KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
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) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 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.04KB 5.36KB
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

…pped in silence

The `object-grid` probe table pinned `expect(errorSpy).not.toHaveBeenCalled()`
for `sort: ['name desc']`, and its comment routed the widening of that refusal
to "that card's business, not this one's". That card landed, so the assertion
and the sentence above it were both false.

The pin now asserts the sink fired exactly once and that the message is the
ARRAY-ENTRY refusal specifically, not merely that `console.error` was called: a
bare call count, or a match on the ruling number, would be satisfied just as
well by the scalar refusal one describe down, and the input here is an array.

`expectNoOrderBy(params)` stays first and stays unchanged. The RETURN value is
the half a later reader is most likely to get wrong, and nothing moved it — a
string entry still names no field and still survives nothing. Loud is not
accepted.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3054.0 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-BQuai_wy.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) 545.91KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
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.83KB 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) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 254.30KB 66.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 137.49KB 34.64KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 113.42KB 27.95KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.05KB 21.01KB
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) 108.85KB 36.01KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
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) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 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.04KB 5.36KB
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-tesla
os-tesla marked this pull request as ready for review September 19, 2026 02:20
@os-tesla
os-tesla added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit ff5ef1c Sep 19, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-9955-array-sort-diagnostic branch September 19, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants