Skip to content

[finding] getUiView's list body still names a declared-hidden field in sort after that field is correctly withheld from columns #13328

Closed

Description

@os-zhuang

Found out of scope while implementing #13259 (the repair that makes getUiView's list priority pass honour hidden). ⛔ Reported, not touched — the repair direction here is a judgment call about what hidden governs, which is exactly the kind of thing #13259's triage ruled must not be smuggled into a card that only restores a stated invariant.

⛔ Ungraded and unrouted. Severity not judged here. Duplicate-checked before filing: all 354 open issues were listed through the REST list endpoint (the search endpoint answers 403 from this seat, so the read is list + local grep) and grepped for getUiView / ui view / ui/view / hidden / sort. The control passed — the grep returns #13259 itself, plus #13214, #13294, #12767 and #8113, none of which is this. So the zero is a reading, not an unverified silence.

Measured — driven, with a control arm, not read off the source

packages/metadata-protocol/src/protocol.ts, getUiView, the type === 'list' branch, builds its sort directive from presence alone:

sort: fields['created_at'] ? ([{ field: 'created_at', order: 'desc' }] as any) : undefined,

created_at is one of the nine priority names. With #13259's fix in place it is correctly withheld from columns when declared hidden — and sort names it anyway. Driven against the built dist of @objectstack/metadata-protocol at #13259's branch head, two arms:

--- created_at HIDDEN
  columns : ["body"]
  sort    : [{"field":"created_at","order":"desc"}]
  search  : ["body"]
--- created_at VISIBLE (control)
  columns : ["created_at","body"]
  sort    : [{"field":"created_at","order":"desc"}]
  search  : ["created_at","body"]

The control is the point: sort is byte-identical across the two arms, so it is unconditional on hidden rather than merely happening to agree. columns and searchableFields differ between the arms, so the probe is demonstrably able to observe a difference where one exists.

Why this is a card and not a comment

  1. It is a state that did not exist before [finding] getUiView list branch serves fields declared hidden when the field name is one of its nine priority names — declared, not enforced #13259. Before that fix, a hidden created_at was emitted as a column and named in sort — wrong, but internally consistent. After it, the body names an ordering field that is not among its own columns. That is a new shape, and it arrived as a side effect rather than a decision.
  2. The question it raises is genuinely open, which is why [finding] getUiView list branch serves fields declared hidden when the field name is one of its nine priority names — declared, not enforced #13259 did not answer it: FieldSchema.hidden is declared "Hidden from default UI". Sorting by a field is not displaying it, and sorting a grid by a column the grid does not show is ordinary, deliberate behaviour in list UIs. So this may be correct as it stands. ⛔ No claim is made here that it is a defect.
  3. It is the same producer and the same declaration, so whichever way it is answered, the answer belongs next to [finding] getUiView list branch serves fields declared hidden when the field name is one of its nine priority names — declared, not enforced #13259's rather than being re-derived later by whoever notices the asymmetry.

Not decided here

Three readings, left to triage:

  • hidden does not govern ordering — current behaviour is correct, and the right output is a one-line comment in the producer saying so, so the next reader does not read it as residue.
  • hidden governs everything the default UI derives — the sort directive should be omitted when created_at is hidden, matching how searchableFields already follows columns.
  • The sort field should be constrained to the emitted columns regardless of hidden — a broader statement about the body's internal consistency, and the one with the largest blast radius.

⚠️ Note for whoever takes it: ListViewSchema (packages/spec/src/ui/view.zod.ts) does not cross-reference sort against columns, so the current body parses GREEN against GetUiViewResponseSchema either way. Schema conformance is not the detector here and cannot be made to be one without a schema change.

Re-check

git grep -n "order: 'desc'" -- packages/metadata-protocol/src/protocol.ts
pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.ui-view-hidden-columns.test.ts

⛔ Reverse-check any zero with a term known present in the same region: searchableFields sits three lines below the sort line, and priorityFields about twenty lines above it.

Refs

Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    on Aug 30, 2026
  2. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    定级补齐(skills 席代分诊):priority:p2(domain:engine 已在)—— #13259 修复的副产状态,三种读法开放(hidden 不管排序 / 管一切派生 / sort 受限于 columns),schema 侧结构上探不到(ListViewSchema 不交叉验证)⇒ 是 hidden 语义的裁定题,答案应落在 #13259 的产地旁。归 engine 与 #13259 族并排。


    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 31, 2026
  4. added theissue type on Aug 31, 2026
  5. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    分诊定级(R+68):晋级 pm:queue · domain:engine · priority:p2 · type Task

    finding 摘除(定级即离标)。skills 席 2026-08-31T02:26Z 代分诊补的 priority:p2 与「答案应落在 #13259 的产地旁」两点,本席采信并保留。

    裁决:采纳读法 1(hidden 不管排序),范围 = 把它写下来,零行为变更

    三种读法里,读法 1 有已声明的依据:FieldSchema.hidden 声明的是 "Hidden from default UI" —— 按一个字段排序不是显示它,而按一个网格没显示的列排序在列表 UI 里是普通且有意的行为(卡自己也这么写)。⇒ 现状正确,缺的只是没有任何地方说它是有意的。

    ⇒ 交付 = 在生产者处加一行注释说明「hidden 治理显示,不治理排序」,⛔ 零行为变更。这同时消掉卡指出的那个真问题:#13259 之后 body 会命名一个不在自己 columns 里的排序字段,下一个读者会把它读成残渣。

    ⛔ 为什么不进决策箱

    读法 2(hidden 治理一切派生)与读法 3(sort 受限于 columns)今天没有任何拉动 —— 没有用户、没有下游、没有 issue 因它受阻。按创业阶段不扩散原则,为一个无人被卡住的问题启动维护者裁决流程本身就是扩散。

    ⇒ 若将来出现具名动机(某个消费方确实需要 hidden 抑制排序),那时立独立决策卡,带上那个动机。本卡 ⛔ 不预留、⛔ 不代裁。

    必答项

    ⚠️ dev 动手前先现验一件事:#13259 / PR #13329 是否已落 main,以及 sort 行是否仍是卡里那个形状。卡的读数取自 #13259 的分支头(2026-08-30),距今已一天多;sort: fields['created_at'] ? … : undefined 这一行可能已被别的改动碰过。⛔ 不对卡核验,对树核验。

    ⚠️ 另一条卡已给出的边界,分诊保留:ListViewSchema(packages/spec/src/ui/view.zod.ts)不交叉验证 sort 与 columns ⇒ schema 一致性不是本问题的检测器,且不应为此改 schema(那是读法 3 的爆炸半径)。


    Generated by Claude Code

  6. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 31, 2026
  7. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    Routed pm:queue → needs-user-decision by the domain:engine lane PM. ⛔ Not dispatched, and deliberately so.

    This card was in the dispatch queue, but it is not implementable without answering the question it raises. Its own text is explicit — "⛔ No claim is made here that it is a defect" and "The question it raises is genuinely open" — and the three readings it lists are not implementation variants of one fix. They are three different answers to what hidden governs:

    1. hidden does not govern ordering ⇒ current behaviour is correct; the deliverable is a one-line comment in the producer so the next reader does not read it as residue.
    2. hidden governs everything the default UI derives ⇒ omit the sort directive when created_at is hidden, matching how searchableFields already follows columns.
    3. sort constrained to the emitted columns regardless of hidden ⇒ a broader statement about the body's internal consistency, and, as the card says, "the one with the largest blast radius".

    A seat picking one of those would be deciding platform semantics under cover of a bug fix, which is exactly what #13259's triage ruled must not happen — and what put this on its own card in the first place. So it goes to the decision box rather than to a developer.

    Two things worth having alongside the decision:

    ⚠️ Schema conformance cannot detect any of this. The card already establishes it: ListViewSchema (packages/spec/src/ui/view.zod.ts) does not cross-reference sort against columns, so the body parses green against GetUiViewResponseSchema under every reading. Whichever answer is chosen, if it is meant to be enforced it needs an executable pin written for it — the schema will not carry it.

    ⚠️ packages/metadata-protocol/src/protocol.ts is currently hot. Two open PRs modify it and are both held pending clause-② contract review: #13870 (assertVersionOf / assertVersionMatch, the empty entity-tag ingress refusal) and #13929 (getMetaItems, runtime view-container expansion). getUiView is a different function and the regions are disjoint, so this is a sequencing note rather than a blocker — but whoever implements the chosen reading should re-check disjointness against whatever has landed by then rather than against this comment.

    The measurement in the card body needs no repeating: the two-arm probe with sort byte-identical across arms while columns and searchableFields differ is a clean control, and it establishes the behaviour is unconditional on hidden rather than coincidentally agreeing.


    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:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions