Measured on @objectstack/spec 17.2.0 + objectui at the current checkout, while authoring a read-only "member detail" record page over sys_user for the Duly app (objectstack-ai/duly#13).
The gap
On a type: 'record' page, exactly one authorable component binds itself to the record in context: record:related_list, via relationshipField / relationshipValueField.
Every other data-bearing component takes a FilterCondition (element:number, and the component-level dataSource binding on anything). The complete dynamic vocabulary for a filter value is CONTEXT_TOKENS — {current_user_id} and {current_org_id} (@objectstack/spec/data, context-tokens.zod.ts), re-exported verbatim by @object-ui/core's filter-tokens.ts. Both name the signed-in viewer. There is no token for the record the page is bound to.
ElementNumberRenderer (packages/components/src/renderers/basic/elements.tsx) confirms it from the other end — it passes props.filter straight to the adapter and adds nothing record-shaped:
const rows = await adapter.aggregate(props.object, {
function: props.aggregate,
filter: props.filter,
…
});
Why it bites
element:number is the only component that renders a computed figure. So "how many open tasks does this person have" — the single most obvious thing to put at the top of a record page — is not authorable. What you get if you write the obvious thing is a count over the whole org, sitting under that person's name, which reads as their number. That is a silent wrong answer, and on the page in question it would also have been a comparison-to-peers the product forbids.
Note the asymmetry that makes it easy to trip over: visibleWhen on the same component does bind record (PageComponentSchema: "Page predicates bind the live page surface: record + current_user plus page state as page.<var>"). So the author can already say whether the block renders based on the record, but not what it counts.
What we did instead
Three record:related_lists (Open / Late / Not moving), each correctly record-scoped and each rendering a real count in RelatedList's badge (the server total). It works and it is honest, but it is three tables where three numbers were wanted.
Suggested shape
A {record_id} filter token alongside the two existing ones, resolved by resolveContextTokens from the record context when one is mounted and refused (loudly, like an unknown token) when it is not. The vocabulary lives in @objectstack/spec, so this needs the spec side too — filing here because the resolver and the record context are both objectui's, and CONTEXT_TOKENS is already consumed as a re-export rather than a fork (objectui#3003).
Whatever the spelling, the failure mode to avoid is the current one: an author writes a plausible filter, gets a number, and the number is about everybody.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Measured on
@objectstack/spec17.2.0 + objectui at the current checkout, while authoring a read-only "member detail" record page oversys_userfor the Duly app (objectstack-ai/duly#13).The gap
On a
type: 'record'page, exactly one authorable component binds itself to the record in context:record:related_list, viarelationshipField/relationshipValueField.Every other data-bearing component takes a
FilterCondition(element:number, and the component-leveldataSourcebinding on anything). The complete dynamic vocabulary for a filter value isCONTEXT_TOKENS—{current_user_id}and{current_org_id}(@objectstack/spec/data,context-tokens.zod.ts), re-exported verbatim by@object-ui/core'sfilter-tokens.ts. Both name the signed-in viewer. There is no token for the record the page is bound to.ElementNumberRenderer(packages/components/src/renderers/basic/elements.tsx) confirms it from the other end — it passesprops.filterstraight to the adapter and adds nothing record-shaped:Why it bites
element:numberis the only component that renders a computed figure. So "how many open tasks does this person have" — the single most obvious thing to put at the top of a record page — is not authorable. What you get if you write the obvious thing is a count over the whole org, sitting under that person's name, which reads as their number. That is a silent wrong answer, and on the page in question it would also have been a comparison-to-peers the product forbids.Note the asymmetry that makes it easy to trip over:
visibleWhenon the same component does bindrecord(PageComponentSchema: "Page predicates bind the live page surface:record+current_userplus page state aspage.<var>"). So the author can already say whether the block renders based on the record, but not what it counts.What we did instead
Three
record:related_lists (Open / Late / Not moving), each correctly record-scoped and each rendering a real count inRelatedList's badge (the servertotal). It works and it is honest, but it is three tables where three numbers were wanted.Suggested shape
A
{record_id}filter token alongside the two existing ones, resolved byresolveContextTokensfrom the record context when one is mounted and refused (loudly, like an unknown token) when it is not. The vocabulary lives in@objectstack/spec, so this needs the spec side too — filing here because the resolver and the record context are both objectui's, andCONTEXT_TOKENSis already consumed as a re-export rather than a fork (objectui#3003).Whatever the spelling, the failure mode to avoid is the current one: an author writes a plausible filter, gets a number, and the number is about everybody.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p