Skip to content

Shipped grids display formula columns the platform cannot sort — a console header click today silently returns unordered rows, and after #9313's widening the personalization PUT it persists is refused with 422 #10235

Description

@os-zhuang

Recorded while re-deriving the #9313 false-positive budget (PR #10234). Not claimed.

What is measured

A synthetic console column-sort PUT for every displayed column of every shipped list surface (180 PUTs across the four authoring lineages: examples/app-crm, examples/app-showcase, examples/app-todo, packages/qa/downstream-contract) yields 3 refusals, all sort-field-unsortable, all on formula columns shipped in grid columns:

  • crm_opportunity.expected_revenue — displayed in examples/app-crm opportunity views (views[2].list and views[2].listViews.all);
  • showcase_project.budget_remaining — displayed in examples/app-showcase project view (views[1].list).

The stored shipped corpus itself is clean — no shipped body declares a sort on a formula field (0 refusals / 0 advisories over all 50 shipped view-door bodies) — so #9313's rollback clause is not triggered. The 3 refusals only arise when a user clicks those column headers.

Why it is a defect today, independent of #9313

Clicking such a header already misbehaves, silently: the engine never refuses the persisted personalization, and a sort on a virtual formula field returns 'asc' and 'desc' in byte-identical order — unordered rows under a success (validate-sortable-fields.ts's own measured message; #6994 / #7095 are the engine-side measurements). The grid offers an ordering the platform cannot honor.

After #9313's widening lands (PR #10234), the same click becomes loud: the console's personalization PUT is refused 422 sort-field-unsortable. Loud beats silent-wrong, but the header should arguably never offer the click.

The open shape question

Where does the fix belong?

  • objectui: the grid marks formula columns non-sortable (no sort affordance on the header). Needs the column's field type, which the served object metadata already carries.
  • framework: serve an explicit per-column sortability signal with the view/object metadata, so the grid does not re-derive "virtual ⇒ unsortable" from field type — declared = enforced, one signal both sides read.

Repro harness: the lane-C section of the measurement script referenced in PR #10234's FP-budget section.

Refs

#9313 (the widening that makes the click loud), PR #10234, #9257 / #6994 / #7095 (the SORT axis measurements), #5074 (the console sort[].id decoration shape).


Generated by Claude Code

Activity

  1. added theissue type on Aug 23, 2026
  2. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Triage: 入决策箱(needs-user-decision + domain:spec,类型 Bug)。「可排序性信号住在哪」是契约位置分叉,人工地板。

    一句话问题:发货示例的表格给公式列显示了排序箭头;用户今天点了,数据静默不排序;#9313 落地后,同一下点击会弹 422 被拒。表头本不该给这个点击——问题是「这列不可排」由谁说:平台在元数据里明说,还是前端自己猜。

    选项(客户可感知代价):

    • A — 契约信号:对象/视图元数据带 per-column sortability 投影,grid 只读信号不猜。代价:spec+rest+objectui 三处动(contract-first 拆分),一次协议加法。
    • B — 前端推导:objectui 按 field type 推「formula ⇒ 不可排」。代价:前端复刻引擎判定规则的影子,引擎规则变化时两边静默漂移——影子逻辑正是历史上反复漂移的那类。

    业务直译:A = 菜单上直接标「售罄」;B = 服务员凭经验猜哪道菜没了。

    四轴:业务需求——用户今天点表头就中招,发货示例(CRM/showcase)全带此坑;长远合理——declared = enforced,一个权威信号两端读,优于影子推导;防 AI 错——前端猜规则是 AI 改 grid 时最容易复制错的逻辑,闭合信号是唯一稳态;阶段聚焦——信号是一个布尔投影不是新能力面,但仍是契约加法,从紧计,故上箱而非直派。

    推荐 A,回退 B(若 v18 前不动契约,B 先止血、A 留卡)。回一个字母即可。

    裁后执行:A ⇒ contract-first 拆分:spec/rest 信号先行(条款②档位),objectui 消费卡带 Blocked-by:;B ⇒ 转 objectui 席单卡派发。信号设计须覆盖「不可排」全集(公式列之外还有哪些类别),由 spec 席在实施卡先枚举。

    [facets-block]

    • 业务需求:指向 A —— 发货示例今天就在给用户不可兑现的排序点击
    • 长远合理:指向 A —— 一个权威信号两端读,优于前端影子推导
    • 防 AI 错:指向 A —— 影子规则是 AI 改动时最易复制错的逻辑
    • 阶段聚焦:弱指 A —— 布尔投影非新能力面,但属契约加法,从紧上箱
      推荐:A(平台元数据带 per-column sortability,contract-first 拆分)· 置信缺口:「不可排」类别全集未枚举;objectui 管道带该投影的成本未测

    Generated by Claude Code

  3. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling recorded (2026-08-23, live PM chat, verbatim: 「其他同意你的意见」— adopting recommendation A): the platform serves an explicit per-column sortability signal with view/object metadata; the grid reads the signal and never re-derives "virtual ⇒ unsortable" from field type. Contract-first split applies:

    • This card = the spec/rest leg (pm:queue, domain:spec, pm:blocking — same stroke). First deliverable inside this card: enumerate the FULL "unsortable" category set (formula columns are one member; the signal must cover the closed set or it is a half-signal), then declare + serve the projection. Clause-② yes (contract addition — contract-review tier).
    • objectui consumer leg filed as a downstream card in objectui with Blocked-by: this one (grid drops the sort affordance where the signal says unsortable; personalization PUT never offered).

    Dev on this leg keeps the #9313/#10234 measurement harness as the oracle (the 3 measured refusal cells must become unofferable clicks downstream).


    Generated by Claude Code

  4. added a commit that references this issue on Aug 23, 2026
    e5ea701
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions