Uh oh!
There was an error while loading. Please reload this page.
fix(data-objectstack): declare the batch write-warning's unattributed-strip placeholder instead of a bare empty string - #7176
Conversation
…-strip placeholder Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
os-warren
commented
Sep 1, 2026
PM review — |
✅ Console Performance Budget
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
Size Limits
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7160
Union of gates below re-run on the final commit
f05427d91.The defect
ObjectStackAdapter.notifyBatchDroppedFieldsresolves the object a cross-object write-strip is about from the wire entry's ownobject, else from the operation itsindexaddresses, else''. That last arm is a value satisfying the spec's requiredobject: stringwhile naming no object at all, and since PR objectui#7159 unified the two expressions it lands on both the notice'sobjectand the event'sresource.The gating measurement — driven, not reasoned
Driven end to end through the real chain:
ObjectStackAdapter.batchTransaction(stubbedclient.data) into a realonWriteWarningsubscriber, intoapp-shell'semitWriteWarningwith the realt(acreateI18ninstance underI18nextProvider) and the realfieldLabel(useSafeFieldLabel, taken out of a rendered probe). Two-op batch:accountcreate +invoiceupdate.getObjectSchemacalledindex: 99, no wireobject{"operation":"create","resource":"","droppedFields":[{"fields":["tax_rate"],"reason":"readonly_when","object":""}]}[]Saved — but some fields did not take effect/Not editable in this record's current state, so it did not take effect: tax_rateindexat all[]index: 1{"operation":"update","resource":"invoice","id":"inv1",...,"object":"invoice"}[["invoice"]]... did not take effect: Localized Tax rateThe control is alive and disagrees on every axis on the same instrument, so the empty call list is a reading and not a broken harness.
index: -1,1.5,0.5andNaNall behave asindex: 99.So the empty string is load-bearing, not inert.
writeWarningToastgates label resolution onadapter && ev.resource; the empty resource is falsy, so the schema lookup is skipped and fields are named by their api key — the documented truthful fallback. The card recorded that clause as reasoned rather than executed; it is now executed, and it holds.Reachability from a real response
Not reachable from a spec-conformant one. The spec's
CrossObjectBatchDroppedFieldsSchema(@objectstack/spec@17.2.0) declares bothobject: z.string()andindex: z.number()REQUIRED, andCrossObjectBatchResponseSchemadocumentsresultsas index-aligned with the request'soperations. Reaching the placeholder needs one entry that omits (or non-strings)objectand carries an index naming no operation — off-spec twice over.Unlike objectui#6889's exotic case this is not structurally impossible: the payload arrives as parsed JSON and a non-conformant server can send it. Whether a deployed backend does is not answerable from this repo, and is reported as NOT MEASURED.
Which side of the refuse/tolerate line — and why it is neither
objectui#6889's repair is deliberately asymmetric with PR objectui#6884's
reasonarm:reasonfrom the future is the producer running ahead of us: expected version skew, so it is tolerated on an explicit arm carrying the wire value verbatim;fieldselement is off-spec input that would reach a consumer typed as a field name: refused here, fixed at the producer.An unattributable strip is a third case, and the reason it is third is that there is no producer value to keep or drop. The response supplied nothing; the only question is what we write. So the answer is declare, not refuse and not widen:
Refuse was measured and rejected. Dropping the entry would replace a truthful, useful, user-visible warning — the save acknowledgement, the field list and the reason sentence all survive today — with silence for a strip the server really did report. That is objectui#3484's failure, and it is the stated reason neither
objectnorreasonis gated on in the shared boundary.Widen was rejected as the banned lenient fallback. Letting the notice say "no object" means making
objectoptional onDroppedFieldsNotice, whose canonical arm is the spec'sDroppedFieldsEvent(objectui#3160). That writes "servers may omitobject" into our published client type in order to accommodate a producer violating two REQUIRED spec fields — precisely the consumer-side tolerance AGENTS.md #0.1 bans, fossilising the producer's bug into a second de-facto contract. The contract-first repair for an off-spec response is at the producer.What this PR does
''with a module-private, documentedUNATTRIBUTED_STRIP_OBJECT, carrying the reachability argument, the refuse/widen rejections, and the reason the value must stay falsy: a namespaced sentinel in the style ofUNRECOGNIZED_DROP_REASONwould be truthful but truthy, and would send the sole consumer togetObjectSchema('objectui:...'). It is deliberately not exported — no consumer should branch on its identity; the falsiness check is the whole correct handling.packages/data-objectstack/src/droppedFieldsUnattributed.boundary.test.ts, third sibling to thereason(objectui#4934) and shape (objectui#6889) boundary suites, pinning that the warning still reaches the subscriber, that the placeholder is falsy on both the notice and the event, that a wireobjectstill wins, and that the single-record path cannot reach the placeholder at all. Every zero carries a live control.The shared predicate was not extended, and did not need to be.
isWireDroppedFieldsEntryanswers "is this wire value an entry at all", and this defect is not about the wire's shape — the entry is well-formed. It is about a resolution step downstream of the gate. Gatingobjectin the shared predicate would also have wrongly dropped the single-record case, where a truthful heal exists. No third predicate was spelled.No published surface moves — proven
packages/data-objectstack/dist/index.d.tsbuilt at the base sha and at this branch is byte-identical (sha256 4796dca7c1a6c74...), 389 declaration lines each, declaration-line diff empty.Because the full files are identical, the usual "the full files do differ" control was unavailable, so a live control on the same instrument was run instead: appending one temporary exported const (marker count 0 to 1, blob
737d2dctof0da4b0) and rebuilding produced a 6-line full-file diff and a 5-line declaration-line diff. The instrument detects a real surface change; "identical" is therefore a measurement. The control was removed anddistrebuilt back to the head hash.Behaviour is unchanged — proven both ways
1to0deleted /0to1injected) and blob hash (737d2dc37cto3b658c6d6e). Result:Tests 2 failed | 35 passed (37)— the two predicted, and no others. Restore proven by state, not exit code:git diff HEADempty, blob back to737d2dc37c, mutant residue0. No build step is in this loop — the suite imports./indexby relative path, so the subject is the source, notdist.1b93160c80, marker count0confirming the swap landed). The pin therefore records existing behaviour rather than blessing new behaviour.Scoped out, filed rather than folded
The same trigger fabricates a second value: with no operation to read,
operationlands on'create'(measured; the control reports'update'). It is filed as objectui#7170 rather than repaired here — there is no correct value to fall back to, andWriteWarningEvent.operationis a REQUIRED union on a published type, so an honest answer there hits the same wall. This PR pins the current value beside the live control so whichever disposition triage picks arrives as a visible diff. objectui#7170 remains open and is not addressed by this branch.Gates run, on
f05427d91pnpm exec vitest run packages/data-objectstack/Test Files 53 passed (53)/Tests 724 passed (724)pnpm --filter @object-ui/data-objectstack type-checktsc --listFilesconfirms the new test file is in the program (1 hit), so this is measured coverage, not an exclusionpnpm --filter @object-ui/data-objectstack lint✖ 421 problems (0 errors, 421 warnings), exit 0 — the warnings are theany-typed harness the sibling boundary suites already usecheck:changeset-presence✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)(empty frontmatter — declared as releasing nothing)check:control-bytes✅ check-control-bytes: OK (scanned 5945 tracked text file(s); skipped 85 binary).check:self-import✅ No package names itself inside its own src/.check:spec-symbols✅ spec symbol derivation: 1332 files scanned against 4959 spec export namescheck:vi-mock-specifiers✅ check-vi-mock-specifiers: OKcheck:phantom-deps·check:esm-specifiers·check:entry-guard·check:vi-mock-inherit·check:side-effects-arraycheck:readme-exportsthe population COLLAPSED -- this run proves nothing, with 353 complaints all of the form "type entry is not on disk -- runpnpm buildfirst" and 0 namingdata-objectstack. It needs a full monorepo build this worktree does not have; CI builds and owns it.Assumption falsified: none of A2.1 to A2.4.
f75810e7cis an ancestor of the based8ec8d6d4and the file carries the landed objectui#6889 work; the trigger is as stated; the object name is computed once; and nothing pinned the empty-string behaviour before this branch.🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code