Skip to content

sys_member list view: a User filter (the searchable user picker), so an organization's members can be found by name from the Members tab's View all #24815

Closed

Description

@objectstack-fleet

Ruled: 6289508074 · letter E′ · 2026-10-20T11:09Z

Problem

On the organization record page (Setup → sys_organization → Members tab), an org with 1,002 members shows a 25-row related list with no way to search. A maintainer asked a plain question — "is this person a member of this org?" — and the only answer today is paging through 40 pages, or going the other way round (open the user's record → Organizations tab).

This is not specific to members: every record:related_list (contacts on an account, lines on an order, team members, …) has the same gap.

Why it can't be fixed by "just turning the magnifier on"

Verified against main (objectstack 0904d85, objectui 2d8a8b7):

  1. objectui — the existing box is client-side and matches raw ids. RelatedList.tsx has an opt-in filterable input, but record-related-list.tsx never passes it, so no page can show it. Even if shown, it sweeps row[key] — for user_id that is usr_…, not the name the cell draws — and while a term is typed it drops out of windowed mode and downloads the whole child collection.
  2. objectstack — $search never traverses a lookup. search-filter.ts expands over the object's own textual/enum fields. sys_member is a junction (two lookups, formula title), so its resolved search set is empty and, since metadata-protocol / objectql: a search term on an object with no searchable field is dropped silently, so the read answers every row (an empty auto-default search set is not refused) #24284, a non-blank search on it is refused 400 INVALID_FIELD. A related list that forwarded the term to the server would get a 400.

Proposal

Part 1 — objectstack: lookup-aware $search (the core)

  • A single-valued lookup in an object's search set matches when its referenced record matches the term on the referenced object's own resolved search fields (e.g. sys_member.user_id → sys_user name / email; pinyin via the __search companion where enabled).
  • Expansion: run the term against the referenced object under the caller's execution context (RLS + FLS apply — otherwise search becomes an existence oracle for records the caller cannot read), then OR { <lookup>: { $in: … } } into the term's clause. One hop only, never recursive.
  • Scale: prefer a semi-join (subquery) when both objects share a datasource; cross-datasource falls back to an id list capped at N (e.g. 500) and the response says the result may be incomplete — never a silent short answer.
  • Resolution: explicit searchableFields may name a lookup; the auto-default adds single-valued lookups whose target has a non-empty search set. sys_member then searches with no metadata change and the metadata-protocol / objectql: a search term on an object with no searchable field is dropped silently, so the read answers every row (an empty auto-default search set is not refused) #24284 refusal no longer fires for it.
  • Record the rule as an addendum to ADR-0061.
  • Free side-benefit: main list views ("search accounts by owner name"), lookup pickers, REST and MCP/AI reads get the same behaviour.

Part 2 — spec: record:related_list.searchable

  • New searchable?: boolean on RecordRelatedListProps, default true; false hides the box. Enforced in the renderer per ADR-0049 (pin test), not declared-only.

Part 3 — objectui: the search box on the related list

Where the user types — in the related-list card header, the same magnifier control the main list toolbar uses:

default
┌──────────────────────────────────────────────────────────────┐
│ 👥 Members  1002                  🔍   [Invite user] [Add member] │

after clicking 🔍 — expands in place inside the header
┌──────────────────────────────────────────────────────────────┐
│ 👥 Members  1 / 1002   [🔍 Zhang Yang        ×]  [Invite user] [Add member] │
├──────────────────────────────────────────────────────────────┤
│ Zhang Yang    Member     2026/10/15 4:30 PM          ···    │
│                          View all results for "Zhang Yang" → │

no match
│        No members match "Zhang San"     [Invite user]      │
Aspect Behaviour
Placeholder derived from the searchable columns, e.g. "Search user, role…"
Trigger 300 ms debounce; Enter searches immediately
Count badge "matched / total" while a term is active
Paging stays windowed — term sent as $search, server pages; never a full download
Clear × or Esc clears and collapses
Tabs each related list keeps its own term
View all link carries the term (?search=…); the list page already reads an initial term
Mobile expands to a full-width row under the header
Hidden when the object resolves no search field (reuse ListView's "searchless object" handling on INVALID_FIELD) or searchable: false
  • Retire the client-side filterable prop (inert today, and wrong if switched on) under enforce-or-remove, after confirming it has no consumers.
  • i18n for placeholder / no-match / incomplete-result strings (en, zh, ja, es).

Acceptance

  1. Org page → Members → 🔍 → type a member's name: only that member is listed, badge shows 1 / 1002, the request is a windowed $search (no full-collection fetch).
  2. A non-member's name: "No members match …" with the card's existing Invite action.
  3. With pinyin search enabled, zy finds 张杨.
  4. A user the caller cannot read is not found by name (RLS/FLS honoured in the lookup hop).
  5. Invitations tab searches by email, Teams tab by name.
  6. "View all" opens the full list with the same term applied.
  7. A business object's related list (e.g. Account → Contacts) is searchable with no metadata change.

Delivery

  1. objectstack PR — lookup-aware $search + ADR-0061 addendum + tests (permissions, cap, pinyin) + spec searchable.
  2. objectui PR — header search box, filterable retirement, renderer wiring, i18n, browser-verified on the org Members tab.

Workaround until then: open the user's record → Organizations tab.


Generated by Claude Code

Activity

  1. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    enhancementNew feature or request
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    [Decision] 关联列表加服务端搜索框、$search 按查找字段所指记录的名字匹配:做不做,做多大?

    Director analysis, batch #377 item 3 — director seat, summon #46, session_013414Vtz3Z8PrXFetUziHoh, 2026-10-20T10:30Z. Triage (triage seat post #6015) routed this card to the box as a feature proposal with no analysis on it ("appetite, yes or no; lean yes, after 18.0"). This seat therefore writes the analysis. Read on objectstack origin/main 41646a5788f5 and objectui origin/main 58798eca8e02, both fetched in this act. Thread: no comments.

    一句话问题: 一个有 1,002 名成员的组织,在组织详情页的「成员」列表里没法搜人;想知道「某人是不是这个组织的成员」,只能翻 40 页。所有关联列表(客户下的联系人、订单行……)都一样。

    背景(本席实测)

    • 服务端搜不了「查找字段的名字」。 packages/spec/src/data/search-fields.ts:查找字段(lookup/master_detail)不进自动搜索集(SEARCH_AUTO_EXCLUDED_TYPES)。显式声明进 searchableFields 的话,匹配的是存储的外键 id,不是名字。sys_member 的 user_id、organization_id 都是查找字段,标题是公式字段(无存储列,sys-member.object.ts:299)。所以按名字搜成员,今天在服务端做不到。卡上说它的搜索集为空、会 400(metadata-protocol / objectql: a search term on an object with no searchable field is dropped silently, so the read answers every row (an empty auto-default search set is not refused) #24284);本席读到 role 是 select,可能仍在自动集里。这点未实测,不影响结论:名字搜不到。
    • 客户端那个框是坏的。 objectui packages/plugin-detail/src/RelatedList.tsx:239/532/660/2280:有个默认关闭的 filterable,在客户端按原始值过滤(查找列上是 usr_… 这样的 id,不是名字),输入时还会退出分页、下载整个子集合。卡上说没有页面传它,本席没另测。

    Governing text

    • ADR-0061 (docs/adr/0061-record-search-architecture.md:50):「For lookup/reference fields, searching the related display name needs a join → Tier 2 (or a denormalized display column).」决定(:13)是 Tier 1 先上、Tier 2 延后。本卡就是 ADR-0061 自己点名延后的那一项,不是新方向。
    • ADR-0061 D1/D2:客户端只发查询词,服务端按对象元数据解析搜索字段,单一解析器喂所有界面。
    • 是否改协议: 服务端规则只改执行(查找字段的匹配语义),不新增 spec 键;卡的 Part 2(新增 record:related_list.searchable 键)是唯一的协议增量,见下。

    前提(复核命令)

    • 查找字段不进自动集:git grep -n "'lookup', 'master_detail'" origin/main -- packages/spec/src/data/search-fields.ts(→ SEARCH_AUTO_EXCLUDED_TYPES)。
    • ADR-0061 把查找名字搜索列为 Tier 2:git grep -n "needs a join" origin/main -- docs/adr/0061-record-search-architecture.md。
    • 客户端 filterable 是本地过滤:objectui git grep -n "filterActive = filterable" origin/main -- packages/plugin-detail/src/RelatedList.tsx。

    选项

    选项 做什么 客户可感知后果 工作量
    A 全案照做 服务端:查找字段按所指记录的搜索字段匹配(一跳、按调用者权限);跨数据源时 id 名单封顶 + 响应标「可能不完整」;spec 新增 searchable 键;objectui 加搜索框 能按名字搜成员;列表页、查找选择器、REST、MCP 同时受益;多一个可关搜索框的元数据键 objectstack engine 1 PR + spec 键 + objectui 1 PR + pin bump + ADR-0061 注记 PR(Tier H);无先例
    B 收窄版 同 A 的服务端规则,但只做同数据源(子查询,带目标对象的权限谓词);跨数据源的查找不进自动集,显式声明则在入口按名拒绝;不加 searchable 键,搜索框在子对象有搜索集时就显示;退役 filterable 与 A 对用户相同;少一个永久维护的键,少一个「结果可能不完整」的新响应字段 engine 1 PR + objectui 1 PR + pin bump + ADR-0061 注记 PR(Tier H);无先例
    C 冗余显示列 ADR-0061 提的另一条路:在子表存一份被指记录的名字 能搜,但名字改了要同步,所有带查找字段的表都多一列 每个对象一次迁移 + 同步机制;最重
    D 暂不做 维持现状,变通是去用户详情页看「所属组织」 大组织继续搜不到人 0

    业务含义直译: A/B = 通讯录里输入「张杨」就能找到他所在的那一行;C = 在每张表上再抄一份名字,名字改了还要挨个去改;D = 继续一页一页翻。

    四轴(业务立场)

    • 项目长远合理性: B(与 A 共有的服务端规则)把 ADR-0061 已经规划的 Tier 2 补齐,一处规则同时喂列表页、选择器、关联列表、REST、MCP,不在客户端各自猜字段。C 把名字复制进每张表,长期同步成本最高。A 的新键与「可能不完整」字段是额外契约面。
    • 实际业务需求: 实测拉动:维护者本人问出的问题,真实组织 1,002 名成员;所有关联列表同病。searchable: false 没有任何人提过要关掉搜索框 —— 零拉动。跨数据源的查找搜索也没有具名需求。
    • 防 AI 犯错: 服务端统一规则 > 客户端各写各的过滤(今天的 filterable 正是客户端猜错字段的样本)。跨数据源在入口响亮拒绝,优于静默封顶再附一个客户端可能忽略的「不完整」标记。权限:查找这一跳必须走目标对象自己的读取(行级与字段级权限都生效),否则搜索就成了「这个名字存不存在」的探测器 —— 出错时是静默泄露,所以这是硬性执行参数。
    • 创业阶段不扩散: B 不加新 spec 键、不加新响应字段;只补一条已规划的执行能力,并退役一个坏掉的客户端开关。A 多两处永久义务。

    推荐:B(18.0 之后,p3)

    • 终态(按①): 两年后,$search 对查找字段按被指记录的名字匹配,这是主流平台的常态(Dynamics 365 的 Quick Find 可以加查找列、按被指记录的主名称匹配;ServiceNow 列表搜索在引用字段上匹配显示值);服务端一条规则,所有界面共享;关联列表的搜索框只是这条规则的又一个入口。
    • 不取 A: 服务端核心相同,但多出零拉动的 searchable 键和「可能不完整」响应字段,都是永久契约。
    • 不取 C: 同步成本最高,ADR-0061 自己也只把它列为备选。
    • 不取 D: 大组织的真实问题继续存在。
    • 置信缺口: 工作量无先例、未实测;同数据源子查询在每种驱动上的写法与性能未测;sys_member 搜索集今天的确切内容未测。
    • 自检:只看①选 B;②③④ 是否翻转:否(② 只把时序放在 18.0 之后)。

    os-decision-facets

    • ① 项目长远合理性:补齐 ADR-0061 已规划的 Tier 2 查找名字匹配,一条服务端规则喂所有搜索界面,缩小客户端各自过滤的特例。
    • ② 实际业务拉动:维护者实测的 1,002 成员组织搜不到人;searchable: false 与跨数据源查找零拉动。
    • ③ 防 AI 犯错:跨数据源在入口按名拒绝,优于静默封顶;查找一跳按调用者权限执行,杜绝静默泄露。
    • ④ 创业阶段不扩散:不加 searchable 键、不加「不完整」响应字段;退役坏掉的 filterable。
      Prior rulings read: lookup,search,related_list,searchablefields → 24 hits; ADR-0061 D2/D3/D7 and :50 (lookup display name → Tier 2, deferred); thread: none; repo: objectstack-ai/objectstack

    裁后执行

    • 裁 B: 本卡改 pm:queue,domain:engine p3,不进 18.0。服务端 PR 先行:单值查找字段一跳、只限同数据源、按调用者上下文经目标对象自己的查询执行(行级与字段级权限都编进子查询;调用者读不到的字段不参与匹配);自动集加入目标有搜索集的单值查找;跨数据源的查找不自动加入,显式声明在入口按名拒绝;四条钉子(按名字命中、读不到的用户搜不到、拼音命中、跨数据源声明被拒)并消融。ADR-0061 加一条带日期的注记,单独开 PR(Tier H,等您批)。随后立 objectui 执行卡(domain:ui,Blocked-by: 本卡):卡片头部搜索框、窗口分页、退役 filterable、四语种文案;再走 console pin bump。
    • 裁 A: 同上,另加 spec 卡(searchable 键,按 ADR-0049 强制)与跨数据源封顶 + 响应字段。
    • 裁 C / D: C 另立迁移设计卡;D 本卡关 not planned,变通写在关卡评论里。

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Director clarification, batch #377 item 3 — director seat, summon #46, session_013414Vtz3Z8PrXFetUziHoh, 2026-10-20T10:57Z. The maintainer asked in this session's chat: 「24815 没看懂,是关联字表中输入文字搜索当前子表吗?不是单表查询吗?为什么不支持」. The answer as given, for the record; the letter is still owed.

    是的,搜的就是当前子表;但「按名字搜成员」不是单表查询。 成员子表的每一行只存一个指向用户的编号,不存名字;名字在另一张表(用户表)里。页面上看到「张杨」,是显示时顺着编号去用户表取来的。

    成员表 sys_member(本页列出的子表)          用户表 sys_user(另一张表)
    ┌───────────┬──────────┬────────┐          ┌───────────┬──────┬────────────────┐
    │ user_id   │ org      │ role   │  ──指向──▶ │ id        │ name │ email          │
    │ usr_8f3a… │ org_01…  │ member │          │ usr_8f3a… │ 张杨  │ zy@example.com │
    └───────────┴──────────┴────────┘          └───────────┴──────┴────────────────┘
       存的是编号,不是名字                          名字在这里
    
    • 在成员表里搜「张杨」,没有任何一列含这两个字,所以搜不到。要搜到,必须两步:先在用户表里找名字含「张杨」、且当前用户有权看到的人,再在成员表里筛出 user_id 属于这些人、且属于本组织的行。这是两张表的查询。
    • 今天服务端的 $search 只搜本表自己存的文本列(ADR-0061 第一层);查找字段按所指记录的名字匹配,ADR-0061 :50 写明「needs a join」,列为第二层并延后。所以不支持。

    关联列表分两种,结论不同:

    • 子表自己存了文本,比如客户下的联系人(联系人表自己有姓名、邮箱)。这是单表查询,服务端今天就支持;缺的只是关联列表上的搜索框(objectui)。
    • 子表是「关系表」,比如组织成员、团队成员。名字在另一张表,需要上面的两步查询。本卡的起因(1,002 名成员的组织)正是这一种。

    对选项的影响: 「只加搜索框」能解决第一种,解决不了本卡的起因;推荐 B 不变(搜索框 + 按所指记录的名字匹配,只限同数据源,不加新 spec 键)。


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Director re-analysis, batch #377 item 3 — director seat, summon #46, session_013414Vtz3Z8PrXFetUziHoh, 2026-10-20T11:03Z. The maintainer proposed in this session's chat: 「成员表 如果配置一个筛选字段,可以弹出搜索用户名不也是可以吗」. This seat measured it; the letter is still owed.

    可以,而且两步都是现成的单表能力。 读数(objectstack origin/main、objectui origin/main 58798eca8e02,本次取):

    • 列表视图已有「用户筛选」:spec UserFiltersSchema(packages/spec/src/ui/view.zod.ts:1755,fields[].field)。objectui packages/plugin-list/src/UserFilters.tsx:141 把 lookup / master_detail / user 字段渲染成 LookupValuePicker,即可按名字搜目标表的选择器。选择器搜用户表是单表 $search;选中后按 user_id 等值筛成员,也是单表。服务端零改动,权限照常(选择器按调用者权限搜用户)。
    • 关联列表的「查看全部」已经带上本组织条件:packages/app-shell/src/views/ObjectView.tsx:2738-2745(?filter[<field>]=<value>),目标列表页支持用户筛选参数(uf_*)。
    • sys_member 今天只声明了一个列表视图 mine(packages/platform-objects/src/identity/sys-member.object.ts:272),没有用户筛选。
    • 关联列表(record:related_list)本身没有用户筛选键。

    新增两个选项

    选项 做什么 客户可感知后果 工作量
    E′(维护者的方案,元数据) 给 sys_member 的列表视图配置 user_id(可加 role)用户筛选 组织页「成员」→「查看全部」(已限定本组织)→「用户」筛选 → 输入名字选人 → 有就一行,没有就空 domain:engine 1 个小 PR(platform-objects 元数据);先例:列表视图 userFilters 现成
    E(就地版) 关联列表复用列表视图同一个 userFilters 形状,在卡片头部就地筛选 不用离开组织页 spec 1 PR(record:related_list 复用现有 schema)+ objectui 1 PR + 元数据 + pin bump;无先例

    推荐改为 E′。 只看①:Salesforce 的关联列表也不就地筛选,「View All」进入完整列表视图再用筛选与搜索;E′ 正是这个建模,且只用已声明的能力,不扩契约。B(按查找名字的自由文本搜索)仍是 ADR-0061 延后的第二层,留待有自由文本拉动时再议,不因本卡实现。②③④ 是否翻转:否(② 「某人是不是本组织成员」由 E′ 精确回答;③ 选择器只能选已有记录,等值匹配,不新增联表读取路径;④ 零新契约)。置信缺口:「查看全部」落到哪个列表视图、该视图上配置的用户筛选是否出现,未在浏览器实测。


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #377 item 3 · letter E′ · maintainer 「OK」 2026-10-20T11:09Z

    Director seat, summon #46, session_013414Vtz3Z8PrXFetUziHoh. Presented in this session's chat as batch #377 item 3: the director analysis 6288891225 recommended B. The maintainer asked 「24815 没看懂…为什么不支持」 (answer 6289320277), then proposed 「成员表 如果配置一个筛选字段,可以弹出搜索用户名不也是可以吗」. This seat measured that proposal as letter E′ and recommended it (re-analysis 6289408661). The maintainer's 「OK」 accepts E′. Thread read through 6289408661 at this write.

    Governing text: ADR-0061 (docs/adr/0061-record-search-architecture.md:50: matching a lookup by the related record's display name "needs a join → Tier 2", deferred). This ruling keeps that deferral. The capability used is the declared list-view userFilters (packages/spec/src/ui/view.zod.ts, UserFiltersSchema).

    What the ruling binds

    1. The change is metadata only. sys_member's list view (packages/platform-objects/src/identity/sys-member.object.ts, listViews) declares a dropdown user filter on user_id, plus role. objectui renders a lookup filter field as LookupValuePicker (packages/plugin-list/src/UserFilters.tsx:141, objectui 58798eca8e02), which searches sys_user by name under the caller's permissions. The member list then filters by equality on the stored id. No server, spec or objectui change.
    2. Measure first, then declare on the right view. The Members tab's "View all" carries ?filter[organization_id]=… (objectui ObjectView.tsx:2738-2745). Which sys_member list view it lands on is unverified. The dev first measures that landing view, then declares the user filter on it. If no declared view is reachable that way, the dev declares one (an all view) rather than editing mine. Then the dev verifies in a browser: org record → Members → View all → User filter → type a member's name → one row; a non-member's name → empty.
    3. Pins. A metadata pin that the landing view declares the user_id user filter, and the browser reading above in the PR. If the landing view cannot be made to show the filter without a renderer or spec change, that premise is falsified: report a fork and do not widen. ⛔ Do not fall back to B or E silently.
    4. Not in this ruling. No free-text lookup-name $search (B stays ADR-0061's deferred Tier 2). No searchable key. No in-place related-list filter (E). The inert client-side filterable prop on objectui's RelatedList is not touched here.

    Workload of the letter: domain:engine · 1 PR (S, platform-objects metadata) · no measured precedent PR; the list-view userFilters capability itself is existing.

    Execution. This card: needs-user-decision → pm:queue, keeping domain:engine, priority:p3 and area:records. The title is rewritten to the ruled scope in the same act. Not an 18.0 gate.


    Generated by Claude Code

  6. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Oct 20, 2026
  7. changed the title [-]feat(search,ui): related lists get a server-side search box, and `$search` matches a lookup by its referenced record's name — an org's 1000+ Members tab cannot answer "is this person a member?"[/-] [+]sys_member list view: a User filter (the searchable user picker), so an organization's members can be found by name from the Members tab's View all[/+] on Oct 20, 2026
  8. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 21, 2026
  9. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 22 (p3 only, by the maintainer's order 「你后续只处理 p3」) · 2026-10-21T05:46Z · p3, domain:engine (triage's grade); ruled letter E′ at 6289508074 (maintainer 「OK」 2026-10-20T11:09Z, on the director's re-analysis 6289408661). An organization with 1,002 members cannot find one of them by name from its Members tab. The ruling answers it with metadata only: sys_member's list view declares a user filter on user_id (and role), so "View all" → User filter → a typed name narrows the list to that member.
    Session: session_01JtSQQ3EWsbNPzyYMug1tXy (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
    Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24815-member-user-filter
    Worktree: objectstack-issue-24815
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface:

    • Measured first (the ruling's item 2):
      • which sys_member list view the organization record's Members tab "View all" lands on. It carries ?filter[organization_id]=… (objectui ObjectView.tsx, read only). sys_member declares only mine today, and mine is filtered to the current user;
      • how a list view's userFilters is declared and rendered: the spec's UserFiltersSchema (packages/spec/src/ui/view.zod.ts), objectui's UserFilters.tsx (LookupValuePicker for a lookup field, read only), and the showcase precedents (examples/app-showcase/src/ui/views/project.view.ts, task.view.ts);
      • what else a new list view or filter on a platform object regenerates or is pinned by: translations, generated bundles, snapshot or metadata pins.
    • packages/platform-objects/src/identity/sys-member.object.ts, listViews only:
      • the landing view declares a dropdown user filter on user_id, plus role;
      • if no declared view is reachable from "View all", declare one (an all view) rather than editing mine, as the ruling directs.
    • Pins: a metadata pin that the landing view declares the user_id user filter. Browser reading in the PR, per the ruling: org record → Members → View all → User filter → type a member's name → one row; a non-member's name → empty.
    • The changeset: patch for @objectstack/platform-objects.

    ⛔ Not in scope (the ruling's item 4):

    • a free-text lookup-name $search: option B stays ADR-0061's deferred Tier 2;
    • a searchable spec key;
    • an in-place related-list filter (option E);
    • objectui's inert client-side filterable prop on RelatedList;
    • any server, spec or objectui change. If the landing view cannot show the filter without one, the ruling's premise is falsified: the dev reports a fork and does not widen, and ⛔ never falls back to B or E silently.

    Container & model: M, mode:cloud (one metadata declaration, then a dev boot and a browser reading), model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier: "no path-derived mandate")
    Clause-②: no (platform metadata only: a list-view user filter, and a list view where none is reachable; no spec key, accept set, response, outcome or export moves)
    Responsibility: an administrator asked "is this person a member of this organization?" must page through 40 pages | sys_member declares no list view that "View all" can filter by user | every administrator of an organization with more members than a page shows
    Thread-read: 6289508074
    Serial constraints cleared: the 11 open PRs' file lists and the pm:dispatched claims were read at this claim, on origin/main 72518d20f5. No open PR touches sys-member.object.ts or packages/platform-objects/src/identity/. No held claim names that file.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-21T05:48Z: mode:cloud. The dev session is session_01FyP8DzyLetcGkWW1Lj2ji8. It builds at the default tier on branch claude/issue-24815-member-user-filter, under claim 6309042170 and the ruling 6289508074 (letter E′).

    • Read first, on a dev boot:
      • which sys_member list view the organization record's Members tab "View all" opens;
      • how a list view's userFilters renders for a lookup field;
      • what a new list view or filter on a platform object regenerates or is pinned by.
    • What lands:
      • the landing view, or a new all view if none is reachable (never mine), declares a user filter on user_id and role;
      • a metadata pin with an ablation, and a patch changeset for @objectstack/platform-objects;
      • the browser reading in the PR body: a member's name gives one row, and a non-member's name gives none.
    • Fork, not fallback: if the filter cannot show without a renderer, spec or server change, the dev reports needs_decision and builds nothing wider.
    • Not built: the free-text lookup-name search (B), a searchable key, an in-place related-list filter (E), objectui changes.
    • Owed: the seat's review of record before the queue.

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24815,
    "status": "needs_decision",
    "branch": "claude/issue-24815-member-user-filter",
    "pr": null,
    "session": "session_01FyP8DzyLetcGkWW1Lj2ji8",
    "premise_still_valid": false,
    "summary": "Measured first, as ruling 6289508074 item 2 orders, and the measurement trips item 3's fork. Metadata alone cannot put a User filter where 'View all' lands, so per the ruling there is no PR, nothing was widened, and nothing fell back to B or E. LANDING VIEW (dev boot, objectstack 72518d2 + console at the .objectui-sha pin cf153eb, showcase pnpm dev -- --fresh -p 38415, admin): org record /_console/apps/setup/sys_organization/record/org_mvhqbbd8hr4cx573 -> Members -> View all lands on /_console/apps/setup/sys_member/data?filter[organization_id]=org_mvhqbbd8hr4cx573. That is the ADR-0055 bare data surface (ObjectDataPage), captioned 'URL-defined data slice - not bound to any saved view'. It is no list view at all: since objectui#12741, RelatedRecordActionsBridge builds View all with recordListPath(), deliberately unanchored so that a default view's own filter cannot over-narrow the list. sys_member's only (hence default) view is mine, which is exactly that case. The director's re-analysis cited ObjectView.tsx's filter[...] reader, but that is the /view route; View all does not go there. The same code is on objectui main 0093b2c. WHAT THE LANDING RENDERS: ObjectDataPage auto-derives its filter bar from the object's fields of type select/multiselect/radio/enum/boolean only (USER_FILTER_TYPES, capped at 4) and reads no declared userFilters. The bar shows 'Role' and no 'User'. PROBE (throwaway; reverted, blob equal to HEAD c3487c97): I declared an all view with userFilters {element:'dropdown', fields:[user_id, role]}, rebuilt, and confirmed the server returned listViews [all, mine]. The View all landing was unchanged: still /data, still 'Role' only. Opened by hand, /console/apps/setup/sys_member/view/all?filter[organization_id]=... renders 'User | Role'. Typing 'Zhang' in the picker offered 'Zhang Yang'; selecting it gave URL ...&uf_user_id=HBxz..., 1 record. Typing 'Olivia' offered 'Olivia Outsider' (a non-member); selecting it gave 'No matching records'. The E-prime mechanism works, but only on a route that View all never opens, and Setup's nav has no Members entry that reaches /view/all. Declaring the view anyway would declare a capability that no user path reaches (PD#10). Making View all reach it needs an objectui change, which the ruling and the claim exclude. Hence needs_decision.",
    "tests": "No code change, so no unit tests. Real runs: (1) pnpm exec turbo run build --concurrency=2 under os-verify-lock: VERDICT command-exit 0, 77/77 tasks; (2) bash scripts/build-console.sh at pin cf153eb: the first try raced my concurrent build (@objectstack/lint DTS TS7016 on a half-built formula dist); the re-run was green ('dist ready ... from objectui@cf153eb2e535'); (3) dev boot, check:console-sha green; (4) Playwright walks, quoted in summary; screenshots kept in the session scratchpad. Probe edit: committed nothing; restored with git checkout HEAD -- sys-member.object.ts; git hash-object = git rev-parse HEAD:path = c3487c977c29d76686580f38008b119fce5109bd (BLOB-EQUAL); git diff HEAD empty. The worktree was removed afterwards. No pin and no ablation: there is no declaration to pin.",
    "gates": "NOT MEASURED, because there is no diff. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exit 2: 'this branch changes nothing against origin/main (merge base 72518d2) - nothing to derive'. No --ran reconciliation, no platform-objects typecheck or test, no test:repo, no merge re-run: nothing changed to run them on. No PR, so no CI check-runs to cite.",
    "dogfood_battery": "n/a - no PR, no ! title, Clause-2 arm none",
    "mcp_calls": "0",
    "api_writes": "1 - POST /repos//issues/24815/comments (this os-dev-report, via the fleet-write relay op comment). Not REST: one git push -u origin claude/issue-24815-member-user-filter, the empty branch at 72518d2, which is the claim's landing marker. No pr_create was spent. All reads were unauthenticated public REST GETs plus a read-only blobless clone of objectui in the scratchpad (now deleted).",
    "line_budget": "none set; diff is empty (0/0, no files)",
    "files_changed": [],
    "deviations": [
    "No PR, no changeset and no pin: the ruling's item 3 fork fired (the dispatch's 'Fork, not fallback'). The draft-PR delivery and the PR-body browser reading are therefore not produced; the browser reading lives in this report's summary.",
    "Dev boot ran in single-org mode. OS_TENANCY_POSTURE=group refuses to boot without @objectstack/organizations, which is not in this repo. So 'an organization with two members plus a non-member' is the Default Organization: two created members (Zhang Yang, Maria Lopez), plus admin and two showcase demo members, and a third created user (Olivia Outsider) removed via /auth/organization/remove-member. Users were created with /auth/admin/create-user, because self sign-up is invite_only.",
    "Throwaway edit to sys-member.object.ts in my own worktree, as a measurement probe; reverted and proven blob-equal; never committed.",
    "Nothing under content/docs/** or docs/qa/** touched."
    ],
    "open_questions": [
    {
    "question": "View all opens the unanchored /data surface, which never reads a list view's userFilters, so E-prime (metadata only) cannot show a User filter there. Which way should the member-by-name need be met?",
    "options": [
    "F1 (objectui, renderer only): ObjectDataPage's auto-derived filter bar also offers single-valued relationship fields (lookup / master_detail / user), rendered by the LookupValuePicker that UserFilters already uses for those types. Zero metadata, spec or server change. Every related list's View all over a junction (members, team members, ...) gains a by-name picker. Cost: 1 objectui PR (domain:ui) + console pin bump + manifest regen. Open detail: the 4-filter cap and field order decide which chips show. On sys_member that is organization_id, user_id, role; the organization chip is redundant beside the URL scope, though harmless.",
    "F2 (objectui + metadata): View all lands on a declared list view carrying filter[...], and this card then declares the all view with the user_id/role user filter (the probe above shows that works). Cost: objectui PR + this metadata PR + pin bump. It reverses objectui#12741's deliberate unanchoring, and it needs a rule for WHICH view (default view = mine here would over-narrow), i.e. a new selection convention or spec key.",
    "F3 (metadata only, E-prime as written): declare the all view anyway. A user can reach it only by typing /view/all; View all and Setup nav never open it. Declares a capability no path delivers (PD#10); not recommended.",
    "D: no change. Workaround: user record -> Organizations tab."
    ],
    "recommendation": "F1. Real need: the measured gap is exactly that the View all landing has no relationship filter; the picker mechanism is already proven (1 row for a member, empty for a non-member). Long-term: it keeps ADR-0055's 'the URL is the view' and objectui#12741's unanchoring intact, and ADR-0061 Tier 2 stays deferred, as the ruling wanted; one renderer rule serves every junction. AI-error axis: nothing to author, so an AI cannot declare a filter the landing silently ignores, which F3 invites and F2 makes depend on an unwritten view-selection rule. Startup axis: no spec key, no server path, no new contract, one renderer rule. Confidence gap: F1 on ObjectDataPage itself is unmeasured. It passes derived userFilters to the same ListView/UserFilters that measured green on /view/all, so the uf
    * equality path should hold, but not yet browser-read there. Ownership moves to domain:ui (objectui), so this card would be re-ruled, not re-dispatched to engine."
    }
    ],
    "out_of_scope_findings": [
    "class: none (drift) - examples/app-showcase/src/ui/views/task.view.ts (the comment above grid) says the console suppresses userFilters on object list views by design. At objectui cf153eb, ObjectView.tsx renders dropdown userFilters there (ADR-0047 amended, objectui #2338) and suppresses only element:'tabs'; the probe saw 'User | Role' render. The stale comment could steer an author away from a working capability. Read-only drift with no producer harmed, so not filed. carrier: none (承接者:无); for Acceptance notes."
    ]
    }


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    [回箱] 裁决 E′ 的前提被实测证伪:组织「成员」页的「查看全部」不经过任何列表视图,按名字找成员要怎么做?

    domain:engine seat 1(seat post #6367)· os-project-manager · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-21T07:01Z。裁决 6289508074(字母 E′)第 3 条写明「前提被证伪就报 fork,⛔ 不硬做,也不悄悄退回 B 或 E」。dev 按此报了 needs_decision(报告 6309736149),没有开 PR,也没有改任何文件。本席复核了它的实测,按 SKILL〈升级与决策〉「仅前提被证伪才回箱」把卡交回决策箱。认领 6309042170 同笔释放。

    一句话问题: 管理员在组织详情页点「成员 → 查看全部」,打开的页面只能按「角色」筛选,不能按「用户」筛选。裁决让我们在成员表的列表视图上加「用户」筛选,但「查看全部」根本不打开任何列表视图,所以加了也没人看得到。

    背景(实测)

    dev 在真实启动的 showcase 上,以管理员身份走了一遍。objectstack 用的是 72518d20f5,控制台按 .objectui-sha 钉在 objectui cf153eb2e535。

    • 「查看全部」落在哪里:
      • 组织详情页 → 成员 → 查看全部,打开 /_console/apps/setup/sys_member/data?filter[organization_id]=…。
      • 这是 objectui 的「无视图数据页」(ObjectDataPage)。页面自己写着「URL-defined data slice - not bound to any saved view」。
      • 这是有意设计的。objectui RelatedRecordActionsBridge.tsx 里「查看全部」用 recordListPath() 生成链接,注释写明不锚定任何保存视图,以免默认视图自带的过滤把列表缩小。本席在 objectui 本地副本里读到了同一段代码。
      • sys_member 唯一的视图 mine(只显示「我的成员身份」)正好就是这种会把列表缩小的默认视图。
    • 这个页面能显示什么筛选:
      • ObjectDataPage 只从字段类型自动生成筛选条,类型只限 select / multiselect / radio / enum / boolean(USER_FILTER_TYPES,最多 4 个)。
      • 它不读列表视图声明的 userFilters。所以页面上有「角色」,没有「用户」。
    • dev 的探针(已撤回,文件与 HEAD 逐字节相同):
      • 声明一个带 userFilters(user_id、role)的 all 视图后,「查看全部」打开的页面完全没变。
      • 手动打开 /view/all?filter[organization_id]=…,页面显示「用户 | 角色」两个筛选。
        • 输入成员 Zhang:选择器给出 Zhang Yang,选中后列表剩 1 行。
        • 输入非成员 Olivia:给出 Olivia Outsider,选中后提示「No matching records」。
      • 结论:机制本身可用,但它只在一个「查看全部」永远不会打开的地址上起作用。Setup 的导航里也没有通往 /view/all 的入口。

    Governing text

    • objectui docs/adr/0055-parameterized-bare-data-surface.md:33:「The URL is the view. filter[<field>]=<value> params (equality) apply…」。「查看全部」的落点就是这个 ADR 定义的无视图数据页。
    • objectstack docs/adr/0061-record-search-architecture.md:50:按查找字段所指记录的名字搜索「needs a join → Tier 2」,延后。原裁决保留这个延后,本次回箱不动它。
    • AGENTS.md Prime Directive chore: version packages #10(:223):「runtime doesn't actually deliver (declared ≠ enforced) — fix it, trim it, or file an issue」。声明一个没有任何用户路径能到达的视图筛选,正是这条要防的「声明而未兑现」。
    • 是否改协议: F1 只改 objectui 渲染器,不加 spec 键,不改服务端。F2 需要一条新的「查看全部选哪个视图」规则,可能要新 spec 键或新约定。

    前提(复核命令)

    • 「查看全部」走无视图数据页:objectui git grep -n "recordListPath(appName, objectName" origin/main -- packages/app-shell/src/views/RelatedRecordActionsBridge.tsx
    • 数据页只从 select/enum/boolean 生成筛选:objectui git grep -n "USER_FILTER_TYPES = new Set" origin/main -- packages/app-shell/src/views/ObjectDataPage.tsx
    • sys_member 只有 mine 一个视图:git grep -n -A3 "listViews: {" origin/main -- packages/platform-objects/src/identity/sys-member.object.ts

    选项

    选项 做什么 客户可感知后果 工作量
    F1 数据页支持关系字段筛选(渲染器) ObjectDataPage 自动生成的筛选条也包含单值关系字段(lookup / master_detail / user),用列表视图已有的 LookupValuePicker(按名字搜、按调用者权限) 组织 → 成员 → 查看全部 → 「用户」筛选 → 输入名字 → 有就一行,没有就空。所有「关系表」的查看全部(团队成员等)同时获得按名字筛选。其他对象的数据页也可能多出关系字段的筛选条,受 4 个上限和排序规则影响 domain:ui · objectui 1 PR + 控制台 pin bump · 无实测先例
    F2 「查看全部」改为落到声明的列表视图 objectui 让「查看全部」打开一个声明过的视图并带上组织条件;本卡再给 sys_member 声明带用户筛选的 all 视图 用户看到的与 F1 相同(探针已证明可用)。但它推翻了 objectui#12741 有意不锚定视图的设计,还需要一条「选哪个视图」的新规则(默认视图 mine 会把列表缩小) objectui 1 PR + 本卡元数据 1 PR + pin bump + 可能的 spec 规则 · 无先例
    F3 照 E′ 原样声明 只加 all 视图和用户筛选 只有手输 /view/all 的人看得到;「查看全部」与导航都到不了。问题对用户依旧存在 domain:engine 1 小 PR · 违反 PD#10,不建议
    D 不改 维持现状 继续用变通办法:打开用户记录 → 「组织」标签 0

    业务含义直译:

    • F1:给通用的「查看全部」列表配一个「按人筛选」的下拉框,任何挂在人或记录上的列表都能用。好比所有表格都能按「负责人」筛。
    • F2:让「查看全部」跳到一个专门为该表准备的视图,再在视图里放筛选。好比每张表都要人工维护一个「全部」页。
    • F3:做了一个没有门的房间。
    • D:继续绕路。

    四轴(业务立场)

    • ① 项目长远合理性: 两年后,平台的通用列表页应该能按关联记录筛选。这是主流平台的标准建模:Salesforce 列表视图的筛选条件可选查找字段;Airtable、Notion 能按关联记录筛选。F1 把这条能力放在通用数据页上,一条规则覆盖所有关系表,并保留「URL 即视图」和不锚定视图两条既有设计。F2 要为每张关系表维护视图,还要新造选视图的规则,特例变多。F3 增加一个无人能到达的声明。
    • ② 实际业务拉动: 有实测拉动。1,002 名成员的组织无法确认某人是否在册,管理员只能翻 40 页。原裁决已经认定这个需求。
    • ③ 防 AI 犯错: F1 没有要写的元数据,AI 不会「声明一个被落点悄悄忽略的筛选」。F3 正是在诱导这种错误:声明了,界面不出现,也没有报错。F2 依赖一条尚未写下的选视图规则,AI 容易写到错的视图上。
    • ④ 创业阶段不扩散: F1 不加 spec 键、不加服务端路径、不加新契约,只多一条渲染规则。F2 新增约定或键,是永久义务。

    只看①选 F1;②③④ 是否翻转:否(②有拉动,③④ 都指向 F1)。

    推荐:F1

    • 置信缺口: F1 在 ObjectDataPage 上的行为尚未在浏览器实测。它把自动生成的筛选交给 /view/all 上已经实测通过的同一个 ListView / UserFilters 组件,uf_* 等值筛选这条路理应成立,但还没读到。
    • 另一处细节: 4 个筛选的上限与字段排序,决定 sys_member 上出现哪几个筛选(organization_id、user_id、role)。其中组织筛选与 URL 里的组织条件重复,无害,但可在 F1 里一并约定隐藏已被 URL 固定的字段。
    • 不取其余选项的理由:
      • F2:推翻一个有意的设计,还要多一条新规则;
      • F3:不兑现的声明;
      • D:问题已经有实测的用户撞上。
    os-decision-facets
    ① 长远:F1 一条渲染规则覆盖全部关系表,保留「URL 即视图」与不锚定视图;F2 增特例与选视图规则;F3 增无人可达的声明。
    ② 拉动:实测有,1,002 人的组织无法按名字确认成员(原裁决已认定)。
    ③ 防错:F1 无元数据可写错;F3 声明后静默不显示;F2 依赖未成文的选视图规则。
    ④ 不扩散:F1 零 spec 键、零服务端、零新契约;F2 新约定或键,永久义务。
    Prior rulings read: "View all related list data page filter", "ObjectDataPage userFilters", objectui#12741 → objectui 13 hits (#12741 reads as the unanchoring's origin; none rules on a data-page relationship filter), objectstack 1 hit (#19514, unrelated); objectui ADR-0055 D1; thread: 6288891225, 6289320277, 6289408661, 6289508074, 6309736149
    

    裁后执行

    • 选 F1:
      • 本卡改路由到 domain:ui(objectui 渲染器),由该 lane 认领。本席不派 objectstack 的 PR。
      • objectui PR 里要附浏览器实测:成员给 1 行,非成员给 0 行。
      • pin bump 随 objectui 的落地流程走。
      • 原裁决里「元数据只此一处」的条款作废。B(自由文本搜索)仍是 ADR-0061 延后的 Tier 2,不动。
    • 选 F2: 先立 objectui 卡,定「查看全部」落到哪个视图的规则(可能还要 spec 卡);本卡挂 pm:blocked 等它,之后本席派元数据 PR。
    • 选 F3: 本席按原 E′ 派一个小 PR,并在 PR 里写明落点到不了。
    • 选 D: 关卡 not planned,变通办法写进关卡评论。

    相关: objectui#12741(「查看全部」不锚定视图的由来),ADR-0061(Tier 2 延后),dev 报告 6309736149。另有一处只读漂移:examples/app-showcase/src/ui/views/task.view.ts 的注释说控制台按设计会在对象列表视图上隐藏 userFilters,但在 objectui cf153eb2e535 上,dropdown 形式实际会渲染。没有生产方受影响,不立卡,只记于此。


    Generated by Claude Code

  13. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Oct 21, 2026
  14. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Maintainer ruling: F1 (the triage seat's chat, 「F1」, on the re-boxed decision 6310115089). Triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-21T08:42Z


    Generated by Claude Code

  15. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Oct 21, 2026
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

    area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landenhancementNew feature or requestpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions