ObjectTree gates its record query on a schema-settled latch (objectui#6014), but the latch is a bare boolean that is never keyed by, or reset for, the object it belongs to. So on an object switch the query fires carrying the previous object's $expand set.
Found while implementing objectui#6453 (the calendar member of the same family). Filed separately rather than folded in: different package, and this is the keying half rather than the missing-gate half.
The shape, on origin/main
packages/plugin-tree/src/ObjectTree.tsx:341 const [objectSchema, setObjectSchema] = useState<any>(null);
:356 const [schemaSettled, setSchemaSettled] = useState(false);
:422 if (!schemaSettled) return;
:423 const expand = buildExpandFields(objectSchema?.fields);
:468 }, [dataConfig, dataSource, schema.filter, objectSchema, schemaSettled, (rest as any).data]);
setSchemaSettled(true) is only ever called with true (line 392, in a finally). Nothing sets it back to false. When the bound object changes, dataConfig changes and both effects re-run — but in the record effect schemaSettled is still true and objectSchema still holds the previous object's fields, so the query goes out as
find(newObject, { $filter: …, $expand: [ …previous object's relation fields… ] })
objectSchema is in the record effect's dependency list, so a correct second query follows once the new schema lands. The damage is therefore one query per switch carrying an expand set for fields the new object does not declare — rejected or silently ignored depending on the adapter — plus the transient it paints.
What the fix looks like
objectui#6271 (ObjectKanban), objectui#6419 (ObjectView) and objectui#6453 (ObjectCalendar) all carry the shape this needs: one piece of state { key, def } | null, with readiness derived during render as resolution !== null && resolution.key === currentKey. Comparing the key during render is what closes the gate in the same commit that changes the object, rather than one commit later. Two separate states (def + hasSettled) cannot express that, which is exactly what is being observed here.
Note the existing finally is correct and must be preserved: every exit settles, including the two early returns and a rejected read, so a tree whose adapter exposes no getObjectSchema still queries rather than waiting forever.
Evidence class
Read from source, not measured. Whoever picks this up should measure the switch on this component before adopting a shape — ObjectTree's record effect has its own dependency set (dataConfig, schema.filter, objectSchema, schemaSettled, (rest as any).data) and neither objectui#6453's numbers nor objectui#6419's transfer.
Blocked-by: #6482
Related: objectui#6271, objectui#6419, objectui#6453, objectui#6014.
Generated by Claude Code
ObjectTreegates its record query on a schema-settled latch (objectui#6014), but the latch is a bare boolean that is never keyed by, or reset for, the object it belongs to. So on an object switch the query fires carrying the previous object's$expandset.Found while implementing objectui#6453 (the calendar member of the same family). Filed separately rather than folded in: different package, and this is the keying half rather than the missing-gate half.
The shape, on
origin/mainsetSchemaSettled(true)is only ever called withtrue(line 392, in afinally). Nothing sets it back tofalse. When the bound object changes,dataConfigchanges and both effects re-run — but in the record effectschemaSettledis stilltrueandobjectSchemastill holds the previous object's fields, so the query goes out asobjectSchemais in the record effect's dependency list, so a correct second query follows once the new schema lands. The damage is therefore one query per switch carrying an expand set for fields the new object does not declare — rejected or silently ignored depending on the adapter — plus the transient it paints.What the fix looks like
objectui#6271 (
ObjectKanban), objectui#6419 (ObjectView) and objectui#6453 (ObjectCalendar) all carry the shape this needs: one piece of state{ key, def } | null, with readiness derived during render asresolution !== null && resolution.key === currentKey. Comparing the key during render is what closes the gate in the same commit that changes the object, rather than one commit later. Two separate states (def+hasSettled) cannot express that, which is exactly what is being observed here.Note the existing
finallyis correct and must be preserved: every exit settles, including the two earlyreturns and a rejected read, so a tree whose adapter exposes nogetObjectSchemastill queries rather than waiting forever.Evidence class
Read from source, not measured. Whoever picks this up should measure the switch on this component before adopting a shape —
ObjectTree's record effect has its own dependency set (dataConfig,schema.filter,objectSchema,schemaSettled,(rest as any).data) and neither objectui#6453's numbers nor objectui#6419's transfer.Blocked-by: #6482
Related: objectui#6271, objectui#6419, objectui#6453, objectui#6014.
Generated by Claude Code