Skip to content

type: 'tree' can only nest by a self-parent pointer — a value-grouped tree is inexpressible, and on an object with no self-reference the view renders flat with no diagnostic #14109

Closed as not planned

Description

@os-warren

Found while building a "what does this team owe" lens in objectstack-ai/duly (issue #12): duties grouped by business unit, then by owner, as a collapsible tree. Measured on @objectstack/spec 17.2.0 and @objectstack/console 17.2.0.

What tree actually does

TreeConfigSchema documents it plainly: "Renders a self-referencing object as an indented, expand/collapse tree-grid. Flat records are nested via a single-parent pointer field (parentField)." The renderer agrees — plugin-tree reads exactly four keys (parentField, labelField, fields, defaultExpandedDepth) and builds the hierarchy by matching record[parentField] against the ids of sibling records of the same object.

That is the org-chart / BOM / category shape. It cannot express the other, at least as common, tree: nest records under synthesized levels derived from field VALUES — unit → owner → duty. Those two levels are not records of the object being listed.

The gap has two halves

1. Inexpressible. grouping: { fields: [...] } is the platform's multi-level value grouping and it works — the grid's grouping hook recurses through grouping.fields building nested subgroups with per-level collapse, which is exactly the wanted behaviour. plugin-tree never reads grouping. So the capability exists in the product; it just is not reachable from the view type whose name promises it.

2. Silent when misapplied. On an object with no self-reference, a type: 'tree' view is not an error and not empty — it is flat. parentField is undefined, the renderer's auto-detect (fields[].type === 'tree', or a lookup whose reference equals the object's own name) finds nothing, every record resolves to a root at depth 0, and you get an ordinary table with an expand gutter that never expands. os validate says nothing: view/layout-without-binding does not cover tree (filed separately), and even if it did, every key in TreeConfigSchema is optional so a present-but-empty tree: {} would satisfy it.

Reproduced on duly_duty, whose lookups point at sys_business_unit, duly_catalog_item and a user — no self-reference. All four gates green.

What we shipped instead

type: 'grid' + grouping: { fields: [{ field: 'business_unit' }, { field: 'owner' }] }. It renders the two-level collapsible hierarchy the card asked for. The cost is that the view type no longer says what the view is, and the tree renderer's indent/expand affordances are not available.

Suggested direction

Either of these closes it; the first is smaller:

  • A — let tree consume grouping. When grouping.fields is present and parentField resolves to nothing, plugin-tree synthesizes one level of parent nodes per grouping field, leaf records at the bottom. The authored shape becomes { type: 'tree', grouping: { fields: [...] }, tree: { labelField } } and reads correctly.
  • B — declare the mode. Add groupByFields: string[] to TreeConfigSchema as the explicit alternative to parentField, and make the two mutually exclusive so a view cannot half-declare both.

Whichever way it goes, the silent-flat half wants a gate on its own: a type: 'tree' whose parentField is neither declared nor auto-detectable on the bound object should warn at author time, because the render is a plausible-looking table rather than an obvious blank.

Related

  • The view/layout-without-binding table omitting tree (and timeline, map) is filed separately.

Activity

  1. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    分诊 → needs-user-decision · domain:spec · p3 · Feature。⛔ 本席不代裁:功能新增类在人工地板上,不论四棱是否同向。

    ⭐ 本卡的另一半已经拆走,今天就在做:「无自引用对象上 type: 'tree' 静默扁平渲染、四门全绿」是 ADR-0078 的缺陷,不依赖本卡的特性裁决 ⇒ 已折进 #14106(pm:queue · domain:spec,同一文件同一规则族),并在那里写明它需要的是第二条检查而不是把 tree 加进绑定块表 —— 因为 TreeConfigSchema 全键可选,一个 tree: {} 就能骗过绑定块检查。⇒ ⛔ 本卡只剩「能不能表达按值分组的树」这一个特性问题。


    一句话问题

    作者想要「先按部门、再按负责人,层层折叠」的清单。平台能渲染这个东西,但只有普通表格视图能;那个名字就叫「树」的视图类型反而做不到 —— 它只会顺着「谁是谁的上级」这种自我指向的关系搭层级。

    选项 × 真实代价

    做什么 真实代价(客户可感知)
    A 让「树」视图也能读平台既有的分组设置(grouping) 一套分组写法,两个渲染器都认。⚠️ 要定义清楚两种搭层级的方式同时写时谁赢,否则作者写了两个、只有一个生效 —— 又是一次静默
    B 在树的配置里另加一个「按这些字段分组」的键 同样能用,但平台从此有两种说「按值分组」的写法,含义相同拼写不同。⇒ 作者(和 AI)每次都要选一次,而两种都对
    C 现在不做。#14106 的诊断落地后,作者写错会被明确告知;想要分组树就用今天已经能用的表格 + 分组 视图类型的名字和它实际做的事对不上(一个逻辑上的树被声明成表格),树渲染器的缩进/展开外观拿不到。⛔ 零风险,⛔ 零进展

    业务含义直译:A = 「树和表格共用一套分组说法」;B = 「树有它自己的一套分组说法」;C = 「先让写错的人收到提示,分组树本身以后再说」。

    四轴(从业务立场)

    • ① 项目长远合理性(权重 ≥50%) — ⭐ 本轴清楚地在 A 和 B 之间排序,却几乎不在「做」和「不做」之间表态。A 是收敛(一套分组词汇覆盖两个渲染器),B 是增生(同一语义两种拼写,永久义务)。⇒ 若要做,A,⛔ 不要 B。但今天的状态并不是「契约增生」,而是「能力缺一块且有替代路径」—— ① 按本义(缩小特例)读,对 C 无异议。⚠️ ⛔ 不得用 ① 的权重给投机扩张背书,规范对此有明文。
    • ② 实际业务拉动 — ⚠️ 弱,而且是本卡自己承认的弱。报告者已经交付了想要的东西:表格 + 两级分组,渲染出的正是那个可折叠层级。⇒ 剩下的损失是「视图类型名字不贴切」+「拿不到树渲染器的缩进外观」= 外观与命名,⛔ 不是做不成事。一个应用,一次,且已绕过。
    • ③ 防 AI 犯错 — ⭐ 本轴已被 view/layout-without-binding covers only 3 of the 6 view types that have a binding block — timeline, tree and map fall through the same trapdoor #14106 基本清偿。本卡里真正危险的是「写了 tree,静默渲染成扁平表」,那是静默容忍;view/layout-without-binding covers only 3 of the 6 view types that have a binding block — timeline, tree and map fall through the same trapdoor #14106 的第二条检查把它变成作者时的响亮拒绝。⇒ 诊断落地后,③ 不再要求这个特性 —— 它要求的那件事已经在做了。
    • ④ 创业阶段不扩散 — 明确指向 C。这是视图授权面的扩张,而替代路径今天就能用、且已在生产里用着。零外部用户证据。

    四轴合议:② 弱且已绕过,③ 已被 #14106 清偿,④ 指向不做,① 对做不做中立(只在做的话排 A > B)。⇒ 按分歧推荐序,零/弱拉动 ⇒ 荐 ④ 不扩散。

    os-decision-facets
    

    四棱(标记见上方代码块 —— ⛔ 不用 HTML 注释形态,该形态实测被本通道清洗,见 #14237)

    推荐:C —— 现在不做,关掉或搁置。 回退:若维护者认为「视图类型名要对得上它做的事」本身值得买,则取 A,⛔ 永不取 B。

    置信缺口(本分析看不见什么):⛔ 我没有测量树渲染器的缩进/展开外观与表格分组的折叠外观在最终用户眼里差多少。本席把这个差异当「外观」,若它实际影响可用性(例如深层级下表格分组很难看清结构),那 ② 的拉动就被我低估了 —— 这一点只有看过两种渲染的人能判。

    裁后我会怎么执行(你不用管)

    裁 C ⇒ 关 not planned,关单评论写明理由与出处(重开免费,维护者可否决);#14106 的诊断照常入队,⛔ 不受影响。
    裁 A ⇒ 摘 needs-user-decision 换 pm:queue,派发令里把「两种搭层级方式同时出现时谁赢」列为必须显式作答项(⛔ 不许留成第三次静默),裁决原话逐字引入;触 packages/spec/src/** ⇒ 条款②路径肢成立,按档位定档。


    查重:与 #14106(绑定块表短)是同一次实测的两张卡,已互相点名;与 #14074(switcher 白名单可视化是否共享 #13748 形状)相邻但不重复 —— 那张问渲染器侧,本卡问授权面。⛔ 无重复。


    Generated by Claude Code

  2. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Sep 1, 2026
  3. added theissue type on Sep 1, 2026
  4. os-project-manager commented on Sep 2, 2026

    @os-project-manager
    Collaborator

    Maintainer ruling recorded — C: not now; closed not planned, reopen is free

    Director seat, summon #8, session session_01ShyhexkB2d1AeRZ85tgAAe, 2026-09-02.

    Provenance (who / verbatim / where): maintainer, live PM chat with the director seat, 2026-09-02, replying to decision batch #3 in which this card was item 4 with the recommendation C (fallback A, never B). Verbatim reply: 「同意」.

    Ruled: C. A value-grouped tree view is not added now. The reporter already ships the wanted two-level collapsible hierarchy as type: 'grid' + grouping.fields; the remaining loss is the view-type name and the tree renderer's indent affordance, and pull is a single application, once, already worked around. The silent-flat half (a tree view on an object with no self-reference rendering as a plain table with all gates green) is not affected by this close — it is carried by #14106, queued in domain:spec, and lands as an author-time diagnostic.

    If this returns with a second pull, the ruled shape is A (let tree consume the platform's existing grouping, with an explicit answer for the case where parentField and grouping are both present) — ⛔ never B (a second spelling of value grouping on the tree config).

    State transition, same stroke: needs-user-decision cleared; closed not planned. Ledger: director seat post #12708, summon #8.


    Generated by Claude Code

  5. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Sep 2, 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

    domain:specDomain lane — seat card indexed by label:pm:seatpriority:p3

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions