Skip to content

Reference rail count badge ignores the FK's relatedListFilter, so it disagrees with the Related tab badge on the same page #6947

Description

@claude

Split out of #4664 (PR #6946), which wired the FK's relatedListFilter into the
derived related list's query AND its tab badge. The reference rail is the third
count surface on the same page and it was left alone on purpose — it cannot be
fixed on the objectui side alone.

What happens

record:reference_rail summarizes the same auto-derived child collections as
the Related tab, and renders a total-count badge per card. Its query is
fixed (packages/plugin-detail/src/renderers/record-reference-rail.tsx):

dataSource.find(entry.objectName, {
  $filter: { [entry.relationshipField]: parentId },
  $top: entry.limit ?? 3,
  $count: true,
})

So on a record page whose FK declares e.g.
relatedListFilter: { status: { $ne: 'deleted' } }, the Related tab badge
and the rail card for the same child now disagree
— the tab counts the
filtered set, the rail counts every child. The rail's own preview rows are
unfiltered too, so the rail is internally consistent; the inconsistency is
between the two surfaces the same page shows at once.

The page synthesizer emits both from ONE options.related array
(buildDefaultPageSchema.ts), so they are demonstrably summarizing the same
collections.

Why it was not fixed in #4664

It is not this repo's call. ReferenceRailEntrySchema is a strictObject with
five keys (objectName, relationshipField, title, limit, displayField)
and no filter, and the shape carries an explicit guidance entry FOR that key
(quoted from @objectstack/spec 17.2.0, dist/ui/index.mjs):

The rail honours no per-entry filter: it issues one fixed query per entry
({ [relationshipField]: parentId }, $top = limit) and reads nothing else
— before this shape existed the key parsed, shipped, and silently filtered
nothing (#8691). record:related_list is the component whose filter is
real; if the rail is ever granted one, this entry shape is where it gets
declared and enforced.

Emitting a filter on a rail entry today would be refused at save (the same
shape that removed icon from rail entries in objectui#5494). So the first
move is an upstream decision on objectstack-ai/objectstack: either grant
ReferenceRailEntrySchema a filter and make the rail honour it, or rule that
the rail is deliberately an unfiltered "everything under this parent" summary
and say so where a reader of the record page can see it.

Suggested resolution shape

Two directions, for whoever triages:

  1. Grant the rail a filter. Upstream spec key on
    ReferenceRailEntrySchema, then the objectui consumption half is small and
    mirrors PR feat(detail): derived related lists consume the FK's relatedListFilter — AND-composed query, badge counts the same set #6946 exactly: carry filter from the derived descriptor into
    the rail entry, and compose it with the parent scope in the rail's own
    probe. Makes all three count surfaces answer one question.
  2. Rule the rail deliberately unfiltered. Then the guidance text above is
    already the whole answer and this issue closes as documentation — but the
    two-badges-disagree behaviour should be stated somewhere a page author
    reads, because it is surprising once relatedListFilter exists.

Not urgent-looking is not the same as not real: the number a user reads off the
rail is wrong relative to what the same page shows them one tab over, and the
severity of a wrong count depends on what was filtered out (soft-deleted rows,
in the driving scenario).

Where

  • packages/plugin-detail/src/renderers/record-reference-rail.tsx (the fixed query)
  • packages/plugin-detail/src/synth/buildDefaultPageSchema.ts (record:reference_rail entries, same options.related source)
  • upstream: ReferenceRailEntrySchema in @objectstack/spec

Filed unassigned by the #4664 execution seat. No dedupe hit: a targeted issue
search for the reference rail's filter/count badge returned 0 results, with a
control query in the same session returning #4664 itself.


Generated by Claude Code

Activity

  1. added
    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

    Anchor read, not guessed. 消费半边落在 packages/plugin-detail/src/renderers/record-reference-rail.tsx ⇒ domain:ui。复核 objectui origin/main a472b07(2026-09-05T00:42:26Z):固定查询就在那里 —— :188 $top: entry.limit ?? 3, / :189 $count: true, ✅。

    定级 p2

    ⭐ 卡面最后那句是对的,本席原样采纳:

    看起来不紧急不等于不真实:用户从导轨上读到的数字,相对于同一页面另一个标签页展示给他的数字,是错的;而一个错误计数的严重性取决于被过滤掉的是什么 —— 在触发本卡的场景里,是软删除的行。

    ⇒ 同一页面上的两个徽章同时给出不同的数字,而页面合成器(buildDefaultPageSchema.ts)从同一个 options.related 数组发出两者 —— 也就是说它们可证明地在汇总同一批集合。不是 p1(没有数据损坏,没有权限问题,导轨自身内部一致 —— 预览行也未过滤)。不是 p3(用户读到的数字是错的,而且软删除行被计入是最坏的那种「错」)。

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

    objectui 侧修不了。 ReferenceRailEntrySchema 是一个五键 strictObject(objectName / relationshipField / title / limit / displayField),没有 filter,而且这个形状专门为这个键携带了一条 guidance(卡面引自 @objectstack/spec 17.2.0):

    The rail honours no per-entry filter … before this shape existed the key parsed, shipped, and silently filtered nothing (#8691). record:related_list is the component whose filter is real; if the rail is ever granted one, this entry shape is where it gets declared and enforced.

    ⇒ 今天在导轨条目上发出一个 filter 会在保存时被拒(与 objectui#5494 移除 icon 同一形状)。⇒ 第一步是上游 objectstack 的一次裁决,不是本仓的一行代码。

    ⚠️ 但这个 blocker 今天没有卡在跟踪它 —— 这才是本条路由要说的重点

    pm:blocked 若指向一个不存在的阻塞物,等于把卡片放进一个没有出口的房间。⇒ ⭐ 本卡的第一项交付不是代码,是在 objectstack 立一张上游卡,把两条路线原样带过去:

    1. 给导轨一个 filter —— 在 ReferenceRailEntrySchema 上加 spec 键并让导轨遵守;此后 objectui 侧的消费很小,与 PR feat(detail): derived related lists consume the FK's relatedListFilter — AND-composed query, badge counts the same set #6946 完全同形(把 filter 从派生描述符带进导轨条目,并在导轨自己的探测里与父作用域合成)。三个计数面回答同一个问题。
    2. 裁定导轨刻意不过滤 —— 那么上面那段 guidance 已经是全部答案,本卡转为文档卡;⚠️ 但「两个徽章不一致」这件事必须写在页面作者读得到的地方,因为在 relatedListFilter 存在之后,它是反直觉的。

    ⛔ 本席不选,也不代立上游卡 —— 立卡是 domain:ui 车道对上游的请求,不是分诊席的产出。⚠️ 但请立了再挂:pm:blocked 附一个 Blocked-by: 才是可追踪的状态,否则下一任分诊席会重新发现同一件事。

    一条本席补的观察

    ⭐ 卡面说导轨「内部一致」(计数与预览行都未过滤)—— 这句成立,而且它其实加强了路线 2 的可行性:导轨从来就是一个「这个父记录底下的一切」的摘要,它自洽。⇒ 真正的缺陷不在导轨,在同一页上两个语义不同的徽章长得一模一样。⚠️ 若裁定走路线 2,那么最小的正确修复可能既不在 spec 也不在导轨查询,而在徽章的呈现(让两者可区分)—— 这条选项两条路线都没列,值得裁决者一并看。

    ⭐ 去重纪律记一笔:"一次针对导轨 filter/计数徽章的定向搜索返回 0 结果,同会话内的对照查询返回了 #4664 本身" —— 带阳性对照的零,本班次一直在要求的标准。

    ⛔ 本席不认领、不派发、不代裁。


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    分诊 — 维护者裁决:不做 · 按 not planned 关闭

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

    维护者裁决(原文)

    分诊席今天把本卡作为待决事项⑤提给维护者,给了两条路:

    • A:给右侧导航栏(reference rail)的条目加 filter,让导航栏计数和「相关」标签页一致。这要改 spec 的 ReferenceRailEntrySchema。
    • B:明确规定导航栏就是「不过滤的总数」,写进文档。

    本席建议 A。维护者答复原文:

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

    ⇒ 本卡不做,按 not_planned 关闭。

    关闭后的现状(照实记录)

    什么情况下重开

    维护者改变主意时,由维护者重开。重开后先在 objectstack 开上游 spec 卡,本卡挂 Blocked-by: 到那张卡。


    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 seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions