Skip to content

fix(core): ValueDataSource clones inline rows with structuredClone, not a JSON round-trip - #9192

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-9175-value-data-source-structured-clone
Sep 11, 2026
Merged

fix(core): ValueDataSource clones inline rows with structuredClone, not a JSON round-trip#9192
os-tesla merged 2 commits into
mainfrom
claude/issue-9175-value-data-source-structured-clone

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Part of objectui#9175. Deliberately not a closing keyword: the ruled change is complete, but one bullet of that card's Acceptance turns out to be measurably unreachable from this card, and the ablation below is the evidence. A maintainer should decide the fate of that card knowingly, rather than have a merge decide it — hence no closing keyword anywhere in this body.

Executes maintainer ruling A on objectui#9061 (2026-09-11, decision batch #115).

What changed

ValueDataSource deep-cloned its inline rows with JSON.parse(JSON.stringify(...)) — in the constructor and again in getAll(). Both are now structuredClone.

The clone exists for one reason, stated in the comment above it: "Deep clone to prevent external mutation". That is an aliasing barrier on a read-only query source. The round-trip was an aliasing barrier too, but it was also a serialization boundary — and nothing asked for one. So every row that reached provider: 'value' silently acquired a requirement the contract never states: ViewData.items is z.array(z.unknown()) in @objectstack/spec, and objectui#6018 pinned the consequence in words — an inline value never has to be serializable at all.

⛔ No try/catch fallback. A function-valued row now throws DataCloneError at construction, loudly, and that is the intended shape: a fallback would restore exactly the silent flattening this replaces.

Measurement first

The two sites, located by text (not by the card's line numbers)

The card measured ~:1077 / ~:1318 and told the dev to re-locate. On origin/main at b17e5e397 they are:

  • packages/core/src/adapters/ValueDataSource.ts:1055 — the constructor.
  • packages/core/src/adapters/ValueDataSource.ts:1296 — the snapshot accessor. It is spelled getAll(), not a getter.

Consumer census

  • Lit controlgit grep -l "ValueDataSource", no pathspec glob: 79 tracked files.
  • Dark control — the fabricated ValueDataSourceZzq: 0 files, exit 1.
  • ⚠️ The glob form is a false-zero instrument, measured on this very census. git grep -l ... -- 'packages/*/src' 'apps/*/src' examples e2e returned 1 file where the un-globbed query returns 102. Every number here is from the un-globbed form.

Three production construction sites in the tree, and no others:

site what reaches items
packages/core/src/adapters/resolveDataSource.ts:70 viewData.items, for provider: 'value'
packages/plugin-designer/src/FieldDesigner.tsx:152 filteredFields — in-memory field metadata
packages/plugin-designer/src/ObjectManager.tsx:101 displayObjects — in-memory object metadata

resolveDataSource is reached from exactly two places — useViewData (packages/react/src/hooks/useViewData.ts:109) and ObjectGantt (packages/plugin-gantt/src/ObjectGantt.tsx:624) — plus the inline arms objectui#9061 / objectui#9136 add. Row readers: find(), which does not re-clone ([...this.items] is a shallow array copy, so rows are the constructor's clones), and getAll(), which has no production reader anywhere in the tree.

Per consumer: what it can carry, and whether it depends on the flattening

consumer can an inline row carry Date / undefined / Map / Set / BigInt / function? depends on the JSON flattening?
route A — resolveDataSourceuseViewData, ObjectGantt, and the objectui#9061 / objectui#9136 inline arms yes. items is z.array(z.unknown()); a host that inlines already-resolved rows hands over whatever it holds — Dates, undefined keys, back-references no — measured
route B — FieldDesigner, ObjectManager no, in practice. Plain metadata: strings, numbers, booleans, nested plain objects no — measured byte-identical under either clone

Both routes are pinned in packages/core/src/adapters/__tests__/ValueDataSource.structuredClone-9175.test.ts, each with the shape it receives today and the shape it receives after.

One dependence on the flattening was found, and it is a pin, not a production consumer — reported rather than papered over:

packages/core/src/utils/__tests__/filter-dialect-equivalence-7221.test.ts pinned NaN as EXCLUDED by is_not_null. Its own docblock already said the exclusion was not the operator's doing: JSON.stringify writes NaN as null, so the row genuinely held null by the time any filter ran. Triaged below.

No production site depends on a Date arriving as a string or on an undefined key vanishing. Both were searched for across the un-globbed census.

Two observable consequences worth naming

Both are through the adapter's own API, not only in the rows, and both are pinned:

  • getObjectSchema infers with typeof, so a Date column infers 'object' where it inferred 'string', and a key whose value is undefined now appears in the inferred schema at all.
  • $orderby on a Date column sorts chronologically instead of lexically over ISO text — the same order for ISO-8601, a different one for any other rendering.

Runtime target — the card's second stop condition

Not triggered. Measured, not assumed:

  • @object-ui/core declares no engines; the root declares node >=22.11. structuredClone is a Node global since 17.0.
  • No browserslist anywhere in the repo — git grep browserslist outside the lockfile returns nothing.
  • target: "ES2020" in tsconfig.base.json is a syntax target. structuredClone is a host global, untouched by downlevelling.
  • It is already an unguarded runtime requirement of published packages: packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx:272 and packages/plugin-designer/src/hooks/useClipboard.ts:30. @object-ui/core itself already calls it, guarded, at packages/core/src/utils/freeze-schema.ts:211.

So: no polyfill, and no new runtime dependency.

Source-text pin census

git grep -l "readFileSync" over the test corpus: 223 files. Lit control lights as specified — the query hits packages/i18n/src/__tests__/residue-namespaces-3546.test.tsx, which names packages/plugin-kanban/src/KanbanImpl.tsx.

No test or script reads ValueDataSource.ts as source text. The only two files naming it by path do so in prose comments. Widening the scope to scripts/ (211 more readFileSync files) surfaced one real registry that does read this file's bytes: scripts/dollar-dialect-alias-census.mjs scans every tracked file for $-dialect operator spellings and classifies packages/core/src/adapters/ as refusal-ledger. This diff adds and removes no operator spelling, so its census is unmoved — but it is the registry the widening was for, and it is named here rather than left unfound.

Ablation — and the one acceptance bullet it falsifies

Measured on a throwaway local merge of this branch with PR objectui#9138's head (7bd112303), created in its own worktree and never pushed. PR objectui#9138's own files came out byte-identical to its head (a git diff over its eight owned paths is empty); the ninth, content/docs/plugins/plugin-calendar.mdx, merged with an unrelated main edit and all ten of the PR's added lines are present.

Both legs run the same two named files (17 tests):

leg the clone in ValueDataSource result
1 structuredClone (this branch) 2 failed / 15 passed
2 JSON round-trip restored on the committed tree 3 failed / 14 passed

Leg 2 reproduces exactly the three standing reds PR objectui#9138 names. Leg 1 turns one of them green:

  1. packages/plugin-map/src/ObjectMap.dataConfigMemo.test.tsx"renders inline data the serializer cannot handle — identity needs no round-trip" (objectui#6018). The one PR objectui#9138 calls "the decision". Red under leg 2, green under leg 1.
  2. packages/plugin-map/src/ObjectMap.filterConfig.test.tsx"really is the DEFAULT config that applies, not 'no config at all'". Red under both legs.
  3. packages/plugin-map/src/ObjectMap.filterConfig.test.tsx"applies schema.map when an array filter is authored alongside it". Red under both legs.

PR objectui#9138's own two new suites (ObjectMap.inlineQueryKeys-9061, ObjectCalendar.inlineQueryKeys-9061, 17 tests) are green on the combined tree.

⚠️ So objectui#9175's Acceptance bullet — "the three red assertions on PR objectui#9138 pass with that diff untouched" — is measurably wrong, and no change to the clone could have delivered it. Cases 2 and 3 are red because PR objectui#9138 starts honouring schema.filter on the inline arm: case 2 authors filter: { map: {...} } against a row that has no map field, case 3 authors filter: [['owner', '=', 'me']] against a row that has no owner. Neither expectation touches serialization. PR objectui#9138's own body already classifies them correctly — "ordinary fixture triage … NOT a second decision" — it only carried them into option A as well, and objectui#9175 inherited that sentence verbatim.

⛔ They are not re-based here. objectui#9175 forbids it, and they are not this card's fixtures — they belong with objectui#9061's own repair.

Both ablation legs proved themselves on disk before anything was read. Mutation: the new text went 1 → 0 and the old text 0 → 1 at both anchors, blob ab57948…2b551d4…. Restore: git checkout HEAD -- ... under an EXIT INT TERM trap with an absolute repo root, verified by an empty git diff HEAD and the blob back at the HEAD blob — never by an exit code. The measurement worktree and its branch are gone.

Fixture triage — one file, re-measured rather than re-based

packages/core/src/utils/__tests__/filter-dialect-equivalence-7221.test.ts instructs its own reader: "A case going red means the measured behaviour moved — re-measure before changing the expectation." Done:

  • NaN is now KEPT by is_not_null. It is neither null nor undefined, and the operator asks about presence — so the new row set is the operator's own answer, and the old one was an artefact of the clone the file itself documented.
  • Both dialect-equivalence contracts (the objectui#7349 regression pins) stay green and untouched. Only the PRESENT_EDGE_IDS constant and the comment explaining it move, and the old set is kept in that comment as the evidence.
  • The docblock's other half is corrected too: the round-trip used to DELETE an undefined value's key, which is why the undefined row and the missing-key row were indistinguishable. They are now distinguishable rows, and is_not_null answers NO to both — which is what the operator is for.

Verification

Run from the repo root; exit codes captured by redirection before any pipe.

what result
the new pin suite 1 passed (1) / 18 passed (18)
packages/core/ + 18 downstream files touching ValueDataSource or provider: 'value' 170 passed (170) / 3537 passed (3537)
the other 60 such files (plugin-gantt, plugin-grid, plugin-list, plugin-map) 60 passed (60) / 683 passed (683)
pnpm --filter @object-ui/core... build exit 0, 202 emitted files verified; dist/adapters/ValueDataSource.js carries structuredClone and no round-trip
pnpm --filter @object-ui/core type-check exit 0 — and tsc -p tsconfig.test.json --listFiles confirms the new test file is IN that program
pnpm --filter @object-ui/core lint exit 0 (563 pre-existing any warnings, 0 errors)
check:control-bytes, check:changeset-claims, check:new-line-citations, check:doc-fences, check:shell-escape-residue exit 0
check-changeset-presence, check-changeset-no-major exit 0
check:governed-queue-guard --test over all five changed paths NOT GOVERNED

The affected-file list was rebuilt without pathspec globs after the false-zero above: 102 test files name ValueDataSource or provider: 'value', and all 102 were run.

NOT MEASURED: check:readme-exports. It requires turbo run build --filter='./packages/*' across every package before it can read a single declared type entry — a farm-scale prerequisite its own dedicated workflow performs. Locally it reports 534 self-import(s) could not be judged … not on disk, which is the prerequisite, not a red. The README edit here adds zero import lines and zero fenced blocks (measured on the diff), so it cannot move that gate's population.

Clause-②: yes

A published adapter's observable behaviour changes — a Date now survives as a Date, and a function-valued row now throws where it was silently stripped. needs:contract-review is on this PR.

⛔ This PR stays draft: no ready flip, no merge queue, no auto-merge.

Changeset: @object-ui/core minor — a widening (more shapes reach the renderer as authored) with one deliberate narrowing (the loud refusal). minor rather than major per this repo's version-alignment rule; the breaking semantics are spelled out as a before/after table in the changeset body. The breakage is the cost of the repair, not its purpose: the purpose is to stop imposing a serialization contract nobody declared, and the loud DataCloneError is what honesty about that costs.

Acceptance notes

  • noted, not filed — objectui#9175's Acceptance bullet about all three assertions is wrong, as measured above. The successor is concrete and already exists: PR objectui#9138 / card objectui#9061, whose own repair owns the two remaining fixtures. Filing a third card would duplicate a correction that belongs in those two.
  • noted, not filedpackages/core/src/utils/freeze-schema.ts:211 still guards structuredClone behind typeof structuredClone === 'function' and falls back to a JSON round-trip. Given the unguarded calls already shipping in @object-ui/app-shell and @object-ui/plugin-designer, that guard is dead weight and its fallback silently reintroduces flattening on a runtime that would already have failed elsewhere. Out of scope here (objectui#9175 is one adapter, and touching cloneAsOverride would widen the contract-review surface). Successor: whoever next edits freeze-schema.ts.
  • noted, not filedgetAll() has no production reader in the tree. Dead-code observation only; ⛔ not a finding class this card files.

Authored by Claude Code for the ObjectUI dev seat; the originating session is https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ.


Generated by Claude Code

…N round-trip

`ValueDataSource` deep-cloned its inline rows through
`JSON.parse(JSON.stringify(...))` in the constructor and again in `getAll()`.
The clone exists only to stop external mutation of a read-only query source —
an aliasing barrier — but the round-trip also made it a serialization
boundary, which nothing asked for.

Consequence: every row routed through `provider: 'value'` silently had to
survive `JSON.stringify`. A `Date` came back as a string, keys whose value was
`undefined` disappeared, `Map`/`Set` flattened to `{}`, `NaN`/`Infinity`
became `null`, and a `BigInt` or a cyclic record graph threw. `ViewData.items`
is `z.array(z.unknown())` in `@objectstack/spec`, and objectui#6018 pinned the
guarantee in words — an inline value never has to be serializable at all.

`structuredClone` is the same aliasing barrier without the serialization
boundary. It still throws `DataCloneError` on a function or a DOM node, and
that stays loud: no try/catch fallback, since falling back to the round-trip
would restore exactly the silent flattening this replaces.

Maintainer ruling A on objectui#9061 (2026-09-11, decision batch #115).

Part of objectui#9175

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
…cument it

`filter-dialect-equivalence-7221.test.ts` pinned `NaN` as EXCLUDED by
`is_not_null`, and its own docblock already said why that was not the
operator's doing: the constructor's JSON round-trip wrote `NaN` as `null`, so
by the time any filter ran the row genuinely held `null`. With the round-trip
gone, `NaN` reaches the matcher as `NaN` — neither `null` nor `undefined` — and
the PRESENCE operator keeps it.

Re-measured rather than re-based: the file's own instruction. Both dialect
equivalence contracts stay green and untouched; only the row-set constant and
the comment that explains it move, and the old set is kept in the comment as
the evidence the exclusion was an artefact of the clone.

Also documents the clone in the adapters README and adds the changeset.

Part of objectui#9175

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: core tests labels Sep 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3091.1 KB 3134.8 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-BHGlgvWX.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) 500.22KB 114.68KB
core (index.js) 8.28KB 3.31KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.41KB 62.43KB
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.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
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.03KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.18KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 252.19KB 65.42KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.94KB 41.04KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.58KB 27.65KB
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.54KB 3.31KB
plugin-view (index.js) 84.42KB 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

PM review — ACCEPT on content; ⛔ arm HELD until CI converges

Read against git merge-base origin/main <head> = b17e5e397, not HEAD^: 5 files, +419/−14. Everything below I measured here.

The executable change ✅

Both round-trips are gone and nothing else moved: ValueDataSource.ts constructor → structuredClone(config.items), getAll()structuredClone(this.items). ⛔ No try/catch, which is the right call and is argued in the comment rather than assumed — a fallback would silently restore the flattening this replaces. The remaining JSON.parse(JSON.stringify in core/src are freeze-schema.ts:213 (guarded, out of scope, correctly named as a successor) and two test-local uses.

The premise correction — independently verified, and it is the most valuable thing in this delivery

The card's Acceptance bullet said the three red assertions on PR #9138 would pass with that diff untouched. The report says only one of the three is a clone problem. I did not take that on the ablation's word — I read the fixtures at source on pr9138 (7bd112303):

// ObjectMap.filterConfig.test.tsx
await renderMap({
  type: 'object-map',
  data: { provider: 'value', items: ROWS },
  map: DECLARED_MAP,
  filter: [['owner', '=', 'me']],
});
expect(markers).toHaveLength(1);

Once PR objectui#9138 makes the inline arm honour schema.filter, that predicate evaluates against rows carrying no owner field and the markers go to zero. The sibling case authors filter: { map: … } against rows with no map field. Neither expectation touches serialization, so no clone change of any kind could turn them green. The correction stands, and it agrees with PR objectui#9138's own body, which already classifies those two as "ordinary fixture triage … NOT a second decision". The card inherited that sentence verbatim and it was wrong.

Part of, not a closing keyword, is the correct choice here.

The pin edit — this is the hazard, and it was handled correctly ✅

filter-dialect-equivalence-7221.test.ts gains 'NaN' to PRESENT_EDGE_IDS. A pin edited on the same PR that changes behaviour is exactly the shape I refuse elsewhere, so I checked it specifically:

  • The pin's original docblock already attributed the NaN exclusion to the clone, not the operator: "NaN is excluded, and NOT because of the operator … JSON.stringify writes NaN as null." Removing the round-trip therefore had to move that row.
  • The dialect-equivalence assertions themselves are untouched — the diff is the docblock plus one array literal.
  • The old row set is kept in the comment as evidence, and the re-measurement explains the second-order consequence too: undefined and missing-key are still both excluded, but no longer for the same reason, because the clone now keeps the key.

That is a re-measurement with its reasoning on the page, not a pin re-based to go green.

Source-text pin sweep — with a correction to the report's version of it

The report swept for pins reading ValueDataSource.ts and found none. True, but narrower than the changed set: this PR also edits packages/core/src/adapters/README.md, and that file is read as source text by at least packages/core/src/utils/__tests__/filter-icontains-alignment-8976.test.ts:76 (ADAPTERS_README_PATH), packages/data-objectstack/src/readme-filter-operator-table.test.ts:87, and the markdown-test registry scripts/markdown-test-inputs.mjs:190.

The conclusion survives, measured rather than assumed: the diff across all three files adds and removes zero $-dialect operator spellings, so the operator-table pins and scripts/dollar-dialect-alias-census.mjs (whose ROLES maps packages/core/src/adapters/refusal-ledger, at :179) are unmoved. Control for the sweep: the same query form hits residue-namespaces-3546.test.tsx:161KanbanImpl.tsx.

⭐ An instrument note I owe from my own run of that sweep: my first pass was | head -20 and the truncation hid the census script entirely — I nearly reported it absent. A truncated sweep is not a sweep. Count the hits first, then page.

Clause-②

check-clause2-carriers.mjs --pair 9192 → both carriers agree.

⛔ Why the arm is held

36 check runs, 4 still running, 0 failing. Not-red-yet is not green — that discipline was bought with objectui#9169, where I armed on a partial read and Test (shard 1/4) went red ten minutes later. I will re-read and arm when pending reaches 0.

On this card's fate after landing

Everything the maintainer's ruling A required is delivered. The single unmet acceptance bullet is not unfinished work — it is measurably unachievable by any clone change, and its two remaining fixtures belong to objectui#9061 / PR objectui#9138's repair. So on landing I will strip the dispatch labels, record the correction on objectui#9061 and PR objectui#9138 where the two fixtures actually live, and close objectui#9175 as delivered rather than leaving it open against a bullet that was wrong when it was written.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation package: core tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants