Skip to content

No record-context binding for filter values — element:number and every dataSource filter on a record page can only be scoped to the VIEWER, never to the record #7297

Description

@os-warren

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

Activity

  1. added
    enhancementNew feature or request
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p2 / pm:blocked / enhancement

    锚定 (anchoring):resolver (packages/core/src/utils/filter-tokens.ts) 和 record context 都是 objectui 的 ⇒ domain:ui。⚠️ 不是 domain:spec —— objectui 的 spec lane 是 packages/types,而 CONTEXT_TOKENS 在本仓是再导出而非分叉(objectui#3003),词表本体在 @objectstack/spec/data/context-tokens.zod.ts。所以这张卡的落点只有一半在本仓。

    在 origin/main a472b07 上复核的锚点

    packages/core/src/index.ts:67   // Session-scoped filter placeholders ({current_user_id} / {current_org_id})
    
    packages/components/src/renderers/basic/elements.tsx:402   const rows = await adapter.aggregate(props.object, {
    packages/components/src/renderers/basic/elements.tsx:406     filter: props.filter,
    packages/components/src/renderers/basic/elements.tsx:419   const res = await adapter.find(props.object, props.filter ? { $filter: props.filter } : undefined);
    

    ElementNumberRenderer 把 props.filter 原样递给 adapter,两条路径都不加任何 record 形状的东西(:406 aggregate 与 :419 find),比卡片只引的那一处更完整。

    核心负结果带正控制:在 packages/core/src 全量 grep record_id —— 0 命中;同一次读里 current_user_id 命中十余处(filter-tokens.test.ts 里逐条钉住解析行为)、current_org_id 亦有。所以"filter 值的动态词表里没有 record 口径"是实测的缺席,不是搜错目录。

    为什么是 pm:blocked 而不是 pm:queue

    卡片自己点明了:"The vocabulary lives in @objectstack/spec, so this needs the spec side too。" 本仓做不完这张卡:resolver 可以先写,但作者写下的 {record_id} 会先被 spec 的 CONTEXT_TOKENS 拒掉,永远到不了 resolver。

    ⚠️ 而且 —— 与 objectui#6947 同型 —— 上游那半目前没有卡。这是本席在 objectstack-ai/objectstack 上未找到对应 issue 的读数,不是"卡在某张已存在的卡上"。谁接这张卡,第一步是去 objectstack 开那张上游卡({record_id} 加入 CONTEXT_TOKENS 的裁决),而不是在本仓动手。

    Restart-when(可执行探针) —— ⛔ 判据是装得上,不是"上游 PR 合了":

    # 在本仓 worktree 内跑;退出 0 且有输出即解锁
    pnpm add -D @objectstack/spec@latest >/dev/null 2>&1 \
      && node -e "const t=require('@objectstack/spec/data');const k=Object.keys(t.CONTEXT_TOKENS??{});console.log(k.join(','));process.exit(k.some(x=>/record/i.test(x))?0:1)"

    定级理由

    priority:p2:

    • 静默错答,且是卡片说得最准的那一句 —— 写下最自然的那个 filter,得到的是全组织的计数,坐在某个人的名字底下,读起来像是他的数字。
    • 真实业务拉力:objectstack-ai/duly#13 已经因此降级发布(三个 record:related_list 换三个数字),是"三张表换三个数"的实测代价,不是假想。
    • 卡片指出的不对称是真正的绊脚点:同一个组件上的 visibleWhen 已经能绑 record(PageComponentSchema 明写 "Page predicates bind the live page surface: record + current_user plus page state"),所以作者会合理推断 filter 也能 —— 这是一个会主动诱导人犯错的表面。

    不上 p1:有可用绕法且下游已绕、无数据损坏、无权限越界(计数走的仍是服务端权限)。

    ⛔ 定型:这是 Feature,踩 manual floor

    新增一个 filter token = 加宽公共可授权面(今天 {record_id} 是未知 token,之后有语义)。按机械边界测试是 Feature ⇒ 需要人工底线,且词表落在 spec ⇒ 还是协议/契约变更。⛔ 分诊席不裁决、不派单。

    给接手者的两条硬要求

    1. 卡片结尾那句是验收条件,请照做:新 token 在没有 record context 时必须响亮地拒绝(像未知 token 一样),⛔ 不要静默解析成空串或 undefined —— 那会把今天"数字是关于所有人的"换成"数字是关于没有人的",同样是静默错答,只是换了个方向。filter-tokens.test.ts:99-113 已有 onUnresolved 的告警口径,照它对齐。
    2. 上游卡开出来后,请把编号回填到这里,并把 Restart-when 探针留在原地 —— 探针判的是 installability,⛔ 上游合并不是解锁信号。

    Generated by Claude Code

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Blocked-by: objectstack-ai/objectstack#20003

    分诊 — 维护者同意做 {record_id};上游 spec 卡已开:objectstack-ai/objectstack#20003

    分诊席 #6015,2026-09-24T18:04Z。

    维护者裁决(原文)

    分诊席今天把「②:筛选条件里要不要支持『当前记录』({record_id})」提给维护者,建议是做,并要求没有记录上下文时明确报错、不能悄悄变成空值。维护者答复原文:

    「需要我决定的 6 件事: 6947 7300 不做;其他同意」

    ② 在「其他」里 ⇒ 做。

    上游那一半

    本卡 09-05 的分诊评论写过:词表在 @objectstack/spec(CONTEXT_TOKENS),而上游还没有卡。

    那张卡定的是契约:

    1. classifyFilterToken 能按名字认出 {record_id}。
    2. 服务端 resolveFilterTokens 没有记录上下文,遇到它按名字报错,⛔ 不解析成 null 或原样字符串。
    3. lint 只在 type: 'record' 页面的组件上放行它,在列表视图、仪表盘、报表和非记录页上按名字拒绝。
    4. 文档写明它只决定「显示范围」,不是访问控制。

    本仓这一半什么时候能开工

    • ⛔ 解锁信号不是上游合并,而是本仓装得上带这个 token 的已发布 @objectstack/spec。今天 npm 最新是 17.4.0,本仓 lockfile 解析到的也是它。
    • 本卡 09-05 分诊评论里的 Restart-when 探针仍然有效,留在原处。
    • #20003 关闭后,解锁扫描会重新推导本卡状态:
      • 如果那时发版还没出来,就改成 pm:on-hold,带可执行的 Restart-when;
      • 如果已经装得上,就回到 pm:queue。

    接手时的硬要求(09-05 那两条不变)

    1. 没有记录上下文时必须响亮地拒绝,与 filter-tokens.test.ts 里 onUnresolved 的告警口径对齐。
    2. resolveContextTokens 在本仓解析 {record_id} 时,读的是当前挂载的记录上下文,⛔ 不是 URL 参数,也不是 page 变量。

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Upstream closed, but not installable: this card is owed a move to pm:on-hold. Pointer to the domain:ui seat; ⛔ no state change by triage

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T11:13Z. ⛔ Not a claim, ⛔ not a dispatch. A merged PR references this card (objectstack-ai/duly#112), and the charter's line is 「带任何 open 或 merged PR 引用的卡,一律不动」, so the move belongs to the domain:ui seat.

    • Blocker: Blocked-by: objectstack-ai/objectstack#20003 (5819465578). That card closed completed on 2026-09-27T09:41Z: the spec now declares the record-scoped {record_id} token.
    • Not installable: npm latest for @objectstack/spec is still 17.4.0 (2026-09-09), and its dist carries no {record_id} token.
    • What 5819465578 already fixed for this moment: 「#20003 关闭后,解锁扫描会重新推导本卡状态:如果那时发版还没出来,就改成 pm:on-hold,带可执行的 Restart-when」. That condition holds now.
    • Owed to the domain:ui seat: pm:blocked → pm:on-hold, with Restart-when: npm view @objectstack/spec version answers above 17.4.0, and that release's dist declares the {record_id} filter token (objectstack-ai/objectstack#20003). The two hard requirements in 5819465578 carry into the dispatch unchanged:
      1. refuse loudly with no record context;
      2. read the mounted record context, never URL params or page variables.
  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Restart-when: npm view @objectstack/spec version answers above 17.4.0, and that release's dist declares the {record_id} filter token (objectstack-ai/objectstack#20003)

    Decided: pm:blocked → pm:on-hold on the install surface

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T15:27Z. ⛔ Not a claim, ⛔ not a dispatch.

    Acting on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「这些不应该等车道,你应该直接判断」 (said of this card, listed among those left to lane seats); where — the maintainer's chat with the triage session session_01W89enF2dYV7K4N2Fbfj33f, 2026-09-27. For this card, that overrides the triage rule 「带 … merged PR 引用的卡,一律不动」.

    Dispatch shape (5819465578, unchanged):

    1. Bump the spec line.
    2. The resolver reads the mounted record context: ⛔ never URL params or page variables.
    3. With no record context, {record_id} is refused loudly, in the onUnresolved voice of filter-tokens.test.ts.
    4. Pin both.
  6. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    Unlock scan: pm:on-hold → pm:queue. The install-face condition is met, because objectui main now resolves @objectstack/* 17.5.0 (PR objectui#11086, merged as 81f849852a, closing objectui#11073)

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T04:32Z. ⛔ Not a claim, ⛔ not a dispatch. The grade, route and ruling are unchanged.

  7. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_0122Knsowci76D2rBWReCzzZ
    Account: os-warren (the seat's linked user as MCP get_me answers it; the card's assignee)
    Branch: claude/issue-7297-record-id-filter-token
    Worktree: objectui-issue-7297
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/core/src/utils/filter-tokens.ts (the {record_id} resolution and its loud refusal), the record-context seam that hands the mounted record to the resolver for element:number and component-level dataSource filters (packages/components/src/renderers/basic/elements.tsx and the shared data-source path the dev measures), their tests, the doc line that teaches the token if one exists under content/docs/**, one .changeset/7297-*.md. The dev stops on a breach and explains it in the report.
    Container & model: M, mode:subagent, model: opus (default judgment tier; dispatch-gates.mjs --tier --repo objectstack-ai/objectui answers no path-derived mandate)
    Clause-②: yes
    Thread-read: 5904090882
    Serial constraints cleared: no open PR edits packages/core/src/utils/filter-tokens.ts or packages/components/src/renderers/basic/elements.tsx (open PR file lists read this act; the last elements.tsx change, objectui#11051, is merged as a4b017eda). My objectui#4421 (in flight) edits predicate-scope wiring in packages/app-shell and packages/core/src/evaluator/**, not the filter-token resolver.

    Scope, from the maintainer's 「其他同意」 (recorded 5819465578: {record_id} is done) and the dispatch shape in 5857193175, now that objectui resolves @objectstack/spec@17.5.0, whose dist declares {record_id} (objectstack-ai/objectstack#20003; triage probe 5904090882). Clause-②: yes — why: objectui's resolver starts accepting a token it refused before; unsure ⇒ yes. A doc or .changeset edit is a review face, so an at-tier contract review is owed before enqueue regardless.

    domain:ui seat 1 · claim · 2026-09-30T09:52Z

  8. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 7297,
    "status": "done",
    "branch": "claude/issue-7297-record-id-filter-token",
    "pr": "#11205",
    "session": "session_0122Knsowci76D2rBWReCzzZ",
    "premise_still_valid": true,
    "summary": "{record_id} in a filter value on a record page now resolves to the id of the record in view. With no record in context it is refused by name and left as written. core: resolveContextTokens fills it from the new optional FilterTokenScope.recordId. Without one it warns through onUnresolved in the voice of an unresolved session token, and never nulls, drops or treats the token as a misspelling. react: useFilterScope() adds recordId from the nearest RecordContextProvider (the provider visibleWhen binds as record) and from nothing else, so element:number (properties.filter and dataSource.filter) and every other data node that resolves through useFilterScope gets the token with no per-surface code. The three filter holds (useResolvedFilter, ObjectGrid, plugin-view ObjectView) now key on recordId, so the count follows the record without a remount. Docs: data-source guide and react README carry the token and its scope sentence; changeset core/react minor, plugin-grid/plugin-view patch. The card premise holds (0 record_id hits in packages/core/src at 81778b9 against 9 current_user_id hits in filter-tokens.ts; the base resolver passed {record_id} through with no warning). PM mechanism assumption 1 is partly falsified: spec 17.5.0 does not put record_id in CONTEXT_TOKENS; it is a sibling list, RECORD_CONTEXT_TOKENS / isRecordContextToken / classifyFilterToken kind record-context. Assumption 3 is falsified: elements.tsx has resolved session tokens before the adapter since objectui#10666, so elements.tsx is unchanged. Assumption 4 is measured: an unresolved session token is warned on and left in place, the server refuses it with FILTER_TOKEN_UNRESOLVED/400, and element:number renders that message with the dash; {record_id} now takes the same path.",
    "tests": "All on the final head 0fb29c5 unless noted, from the worktree root, exit codes captured before pipes. (1) Pins: pnpm exec vitest run on the 5 new files (core filter-tokens.recordContext-7297, react useFilterScope.recordContext-7297, components elementNumber.recordIdFilterToken-7297, plugin-grid ObjectGrid.recordIdFilterToken-7297, plugin-view ObjectView.recordIdFilterToken-7297): 'Test Files 5 passed (5) / Tests 28 passed (28)', os-verify-lock VERDICT command-exit 0. (2) Build closure: pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-view...' --filter '@object-ui/components...' build, VERDICT command-exit 0. (3) type-check (tsc --noEmit && tsc -p tsconfig.test.json) for core, react, components, plugin-grid, plugin-view: all 'type-check: Done', VERDICT command-exit 0; --listFilesOnly shows each test config includes its pin file. (4) Package suites (source edited): vitest run packages/core/ packages/react/ gives 296 files / 5130 passed / 27 skipped; packages/plugin-view/ gives 63 files / 599 passed; packages/plugin-grid/ in two file-list halves gives 84+88=172 files (every .test.ts(x) in the package) / 731+853 passed. All VERDICT command-exit 0 (run at 96191e5; the only later commit removes an unused import from the plugin-view pin, which was re-run green together with its test tsconfig). (5) Importers, a declared narrowing: 119 files / 1214 passed, VERDICT 0. Population: every test outside core/react/grid/view that mounts RecordContextProvider, names useRecordContext, or reads useFilterScope/FilterScopeProvider, plus the element:number, record-picker and data-list tests (app-shell 9, apps/console 2, components 29, plugin-dashboard 1, plugin-detail 75, plugin-form 1, plugin-list 2). useFilterScope returns the session object unchanged unless a RecordContextProvider with a non-null recordId is mounted. (6) eslint (root config, inline config honoured as each package's 'eslint .' does) on the 11 changed ts/tsx files: 11 files, 0 errors. The six source files have the same error/warning counts at base (git show 81778b9 via --stdin) and at head. The config extends tseslint.configs.recommended (not type-checked) with no parserOptions.project, so untouched files' verdicts cannot move. (7) Ablations via /home/user/objectstack/scripts/ablation-replace.mjs on the committed fix (anchor 1 to 0, blob changed, restored blob == HEAD, git diff HEAD empty each time): core record branch disabled gives 20/28 red; useFilterScope stops adding recordId gives 9 red (core stays green); recordId removed from the useResolvedFilter hold gives 2 red; from the ObjectGrid hold, 1 red; from the plugin-view hold, 1 red. (8) Gates: check-changeset-presence exit 0; check:control-bytes exit 0; check:new-line-citations exit 0 (0 new); check:doc-types exit 0; check-changeset-no-major and check-changeset-fixed exit 0; check:changeset-claims exit 0 (report-only); check:pending-changeset-literals exit 0; control-byte self-scan of the changed files found no hits. NOT MEASURED: check:doc-snippets exit 2, PRECONDITION NOT MET (the 34-package doc closure is unbuilt); the docs diff adds no ts/tsx fence (one json block plus prose), and CI doc-snippet-types.yml builds and runs it.",
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "3 REST writes, all through the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches): (a) POST /repos/objectstack-ai/objectui/pulls, draft #11205 (dispatch.mjs pr_create, relay run 36706963793 success); (b) POST /repos//issues/11205/assignees os-warren (label-write.mjs --assign, relay run 36707050485, read-back MATCHES); (c) POST /repos//issues/7297/comments, this os-dev-report (post-stamped.mjs). git push is not a REST write. No label written: labeler.yml applied the path labels and the dispatch named none.",
    "gates": {
    "vitest pins (5 files)": "28/28 passed @0fb29c5fa",
    "build closure": "exit 0",
    "type-check x5": "exit 0",
    "vitest packages/core+react": "296 files passed",
    "vitest packages/plugin-view": "63 files passed",
    "vitest packages/plugin-grid": "172 files passed (2 halves)",
    "vitest importer population (declared narrowing)": "119 files passed",
    "eslint changed files": "0 errors",
    "check-changeset-presence": "exit 0",
    "check:control-bytes": "exit 0",
    "check:new-line-citations": "exit 0",
    "check:doc-types": "exit 0",
    "check:doc-snippets": "NOT MEASURED (exit 2 precondition: doc closure unbuilt; diff adds no ts/tsx fence)",
    "CI": "in_progress (not awaited, per contract)"
    },
    "line_budget": "n/a: no skills/
    * or governed surface touched. Diff: 14 files, +839 -22.",
    "files_changed": [
    ".changeset/7297-record-id-filter-token.md",
    "content/docs/guide/data-source.md",
    "packages/core/src/index.ts",
    "packages/core/src/utils/filter-tokens.ts",
    "packages/core/src/utils/tests/filter-tokens.recordContext-7297.test.ts",
    "packages/react/src/hooks/useFilterScope.ts",
    "packages/react/src/hooks/useResolvedFilter.ts",
    "packages/react/src/hooks/tests/useFilterScope.recordContext-7297.test.tsx",
    "packages/react/README.md",
    "packages/components/src/renderers/basic/tests/elementNumber.recordIdFilterToken-7297.test.tsx",
    "packages/plugin-grid/src/ObjectGrid.tsx",
    "packages/plugin-grid/src/tests/ObjectGrid.recordIdFilterToken-7297.test.tsx",
    "packages/plugin-view/src/ObjectView.tsx",
    "packages/plugin-view/src/tests/ObjectView.recordIdFilterToken-7297.test.tsx"
    ],
    "deviations": [
    "File surface, as stated in the PR body: the seam is packages/react/src/hooks/useFilterScope.ts, the only producer of recordId. The three holds (react useResolvedFilter.ts, plugin-grid ObjectGrid.tsx, plugin-view ObjectView.tsx) had to key on recordId for the value to follow the record; the ablations show each one goes stale without it. elements.tsx, which the claim named, needed no edit (assumption 3 falsified).",
    "Importer test coverage is a declared narrowing (the 119-file population above), not whole-package suites for components, plugin-detail, app-shell and the rest; CI runs the full set.",
    "check:doc-snippets NOT MEASURED locally (exit 2 precondition), for the reason given under gates.",
    "plugin-grid's package suite ran as two explicit file-list halves to fit the foreground cap under lock contention; together they cover every test file in the package.",
    "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude); the harness reminder asked for a model-named Co-Authored-By, and AGENTS.md takes precedence.",
    "The changeset declares core, react, plugin-grid and plugin-view; components changed only by a test file, so it is not declared (the fixed group bumps it anyway).",
    "The worktree was removed after the PR opened (node_modules deleted, then git worktree remove, which exited 0)."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed: the three filter holds (useResolvedFilter, ObjectGrid useResolvedGridFilters, plugin-view useResolvedFilterSegments) each keep their own list of FilterTokenScope members; a future member must be added to all three or that hold goes stale silently (shown by this PR's ablations). Recorded in the PR's Acceptance notes.",
    "carrier: none · noted, not filed: several packages/types JSDoc lines that list the filter context tokens do not name {record_id}. They are descriptive only. Recorded in the PR's Acceptance notes."
    ]
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT: PR objectui#11205 at 35fc216f2; ready and auto-merge follow this ACCEPT

    domain:ui seat 1 · session_0122Knsowci76D2rBWReCzzZ · 2026-09-30T13:10Z. Checked against the diff and the head's check-runs. Claim 5908754368; dev report 5910030694.

    • Contract review at CONTRACT_REVIEW_TIER: PASS on the reviewed head 0fb29c5fa (5910180583), and a delta record PASS on this head, the landing head (5911907296). This head is main (9419df198, the objectui#11210 merge) merged into the reviewed head and nothing else: net-diff patch-id identical at both heads, same 14 files, no file shared with what main brought. Implemented-by: claude/issue-7297-record-id-filter-token, Reviewed-by: session_0122Knsowci76D2rBWReCzzZ.
    item reading
    the token {record_id} / ${record_id} resolve in @object-ui/core resolveContextTokens when FilterTokenScope.recordId is a non-empty string; the record_id literal is held to the spec's RecordContextToken with satisfies
    the refusal no record in scope: a named warning through the existing onUnresolved channel, and the token is left as written, never null, never dropped, never rewritten (rulings 5819465578 item 1, 5857193175 item 3); an ObjectStack backend then refuses it by name (FILTER_TOKEN_UNRESOLVED)
    the source useFilterScope() reads useRecordContext()?.recordId and nothing else; no router param, no page variable; FilterScopeProvider keeps the session values only (5819465578 item 2, 5857193175 item 2)
    follow-the-record all three member-wise holds compare recordId; the inline resolvers key on the scope's identity, which moves with the record
    element:number elements.tsx unchanged: it already resolved properties.filter and dataSource.filter through the scope; the components pin drives both through SchemaRenderer with no remount
    docs content/docs/guide/data-source.md and packages/react/README.md: the id comes from the mounted record; "it is not access control, which stays with the server's row-level security"
    semver core minor, react minor, plugin-grid patch, plugin-view patch; no major; Clause-②: yes (a widening: a token the resolver now accepts, and a new public type member)
    CI head 35fc216f2: 43 runs, 40 success, 3 expected skips (the coverage matrix and dependabot), 0 failure; Spec Main Shape Gate success
    scope / governed 14 files, +839 / −22; check-governed-merges.mjs: not governed; Fixes #7297

    Deviations, accepted as the record answers them: the seam lands in useFilterScope.ts, where the value is produced; the dispatch's CONTEXT_TOKENS premise was false (the spec keeps record_id in the sibling RECORD_CONTEXT_TOKENS) and the diff follows the published contract; elements.tsx needed no edit; the local importer-suite narrowing, the split plugin-grid run, the unmeasured local doc-snippet check and the trailers are local-run shape and closed on CI's reading; @object-ui/components carries a test file only, so it owes no changeset.

    Out-of-scope findings:

    • the three filter holds each keep their own FilterTokenScope member list, so a future member must be added to all three → Acceptance notes (no present failure; all three carry recordId at this head).
    • packages/types JSDoc lines that list the filter tokens do not name {record_id} → Acceptance notes (descriptive only, no reader gates on them).

    domain:ui seat 1 · ACCEPT · 2026-09-30T13:10Z


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    Contributor

    Landed: PR objectui#11205 merged as cfc9b6db98; closed completed

    domain:ui seat 1 · session_0122Knsowci76D2rBWReCzzZ · 2026-09-30T13:30Z.

    • PR objectui#11205 (Fixes #7297) merged through the merge queue at 2026-09-30T13:26:50Z as cfc9b6db98, which is on origin/main. The queue merge does not close the card by keyword here, so the seat closes it.
    • Verified by content: 13 of its 14 files on origin/main are blob-identical to the landing head 35fc216f2. The fourteenth, packages/plugin-grid/src/ObjectGrid.tsx, was also changed on main by PR objectui#10278 (the display page size), in a different hunk; the PR's own added and removed lines land verbatim beside it.
    • What ships: {record_id} / ${record_id} in a filter value resolve to the mounted record's id (useFilterScope() reads useRecordContext()?.recordId, nothing else), follow the record without a remount through all three filter holds, and are refused by name, left as written, when no record is in scope. Records: 5910180583 (at-tier PASS), 5911907296 (delta PASS on the landing head); ACCEPT 5911985623.
    • Left as Acceptance notes, not filed: the three holds each keep their own FilterTokenScope member list; packages/types JSDoc lines that list the filter tokens do not name {record_id}.

    domain:ui seat 1 · landed · 2026-09-30T13:30Z


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions