Skip to content

finding(plugin-grid): after #8767 the header arrow still reads the retired string sort spelling the fetch path now refuses — parseSchemaSort is wider than the wire #8961

Description

@os-warren

维护者速读

一个视图写着「按名称倒序」(旧拼法),表格的名称列上就会在任何人点击之前亮起一个倒序箭头 —— 而发给服务端的那次查询里根本没有排序。箭头在陈述一件关于这份列表的事,而这件事不是真的。

这是 #8767 route C 的已知留尾:取数那条路已经按裁定拒收旧拼法了,表头箭头这条路没有,两个读者对同一个键给出相反的解释。⭐ 本席今天在 origin/main 88561fdc48 上复测,两半都还在:表头读取器仍然把字符串拆成「字段 + 方向」,而取数路径的 pin 明文写着 REFUSES。

⚠️ 错是静默的:用户看到一个自信的箭头,没有任何报错;唯一的响亮信号是控制台里一行日志,没有人看。

三个选择:

  • A —— 只把箭头收窄:让表头也只认新拼法,旧拼法不再点亮箭头。今天就能把这个假箭头关掉,改动小、可逆;代价是这块仍然是全平台唯一一处私有的排序下沉路径 —— 统一那件事留给下一张卡。
  • B —— 整块并回共享通道:把这个键的读取与下沉全部走共享汇点,一个键一个解释。根治;代价是要改这块私有的传输形状(拼接串 → 映射表),而这正是你在 2026-09-10 点名拒绝的 route B,前置是一次服务端排序契约的实测,那个读数今天还没有。
  • C —— 暂不动:接受这个假箭头,等排序键的统一重构轮一起做。零成本;代价是用户今天继续看到一个说谎的箭头。

我的建议是 A(回退:C)。提卡的开发席建议把 A 与 B「当作一道题一起裁」,因为两者耦合 —— 我同意耦合,不同意时间:假箭头是用户今天看得见的,而 B 的前置读数你已经推迟过一次。A 不挡 B,A 落地之后 B 的范围一个字都不变。

选哪个:A / B / C?

os-decision-facets

  • ① 项目长远合理性:A 关掉一个说谎的读数,但保留「一个键两个读者」这个结构;B 消灭这个结构;C 什么都不动。这一轴单独看指向 B。
  • ② 实际业务拉动:今天就有人撞上 —— 任何仍写旧拼法的视图,表头都指着一个不存在的排序(已定级 p2、用户可见)。这一轴指向「今天就做」,也就是 A。
  • ③ 防 AI 犯错:这是静默容忍的典型 —— 界面自信地画出箭头,没有任何人会看见这个错;唯一的响亮信号在控制台。A 让旧拼法在表头上也不再被接受,与取数路径口径一致;B 让整个键只剩一个解释。拖着不裁是三者里最差的,因为它把「两个读者各说各话」这件事变成常态。
  • ④ 创业阶段不扩散:A 与 B 都不新增任何面;C 让两个读者对同一个键的分歧继续存在,而这正是永久义务的形状 —— 以后每一次动排序,都要同时记得这两处。
  • 推荐:A(回退:C)。
  • 本分析看不见什么:我没有枚举今天还有多少视图在写旧拼法 —— 如果是零,这张卡就从 p2 掉成清理项,答案可能翻向 C;我也看不见服务端排序契约的实测(B 的前置),那正是 2026-09-10 那次推迟点名要的读数,而它不在这个仓里。

Re-measured 2026-09-15T07:52Z by the domain:spec @ objectui PM seat — both halves still stand

On origin/main at 88561fdc48:

① The header reader still parses the retired spelling. parseSchemaSort in packages/plugin-grid/src/ObjectGrid.tsx opens by wrapping a bare string into a one-element list and then splitting each string entry on whitespace into a field and an order, defaulting the order to asc unless the second token lowercases to desc. ⇒ sort: "name desc" still yields a descending indicator for name.

② The fetch path refuses it, and says so in its own pin. gridRetiredStringSort-8767.test.tsx states, verbatim: "object-grid REFUSES the retired string sort clause (objectui#8767)", and pins that "a string sort reaches no $orderby, and says so once per spelling". The same docblock records the declination this card sits behind: "the sink's {field: direction} map is route B on objectui#8767, declined by the maintainer 2026-09-10 pending a card that measures the server contract and both readers."

③ The probe the filer left in the tree is still there. serverSorting.test.tsx carries the case shows the view's declared sort before anyone clicks, alongside replaces the view's declared sort rather than stacking on it.

⚠️ Instrument note, recorded because the first reading was wrong. Searching that file for the case name with a plain apostrophe returned zero — an artifact of the source escaping the apostrophe inside a single-quoted string, ⛔ not an absence. The zero was caught by a control (the same file has 12 it( cases). Same class as this seat's cross-line-phrase lesson: a literal search over escaped source is a different instrument from the text it is searching for.

⚠️ One stale in-tree reference, for opportunistic repair. gridArrayArmOrderby-8973.test.tsx's docblock describes this card as pm:blocked; it carries needs-user-decision today. Whoever next edits that file should reconcile it — ⛔ not a sweep, and ⛔ not this card's work.

Controls. Firing control: the same instrument resolves the real registration and the real refusal pin quoted above. Absent-token control: mfxq_absent_ctl_20260915a → 0 files on the same tree (single use — publishing it here spends it, and this seat will mint a fresh one next time).


Filed unassigned by the os-dev seat that implemented objectui#8767 (session session_01Jmxdo7bmeqCQHLSfmLVX9w, branch claude/issue-8767-object-grid-refuses-string-sort, PR #8960). Not claiming; grading is the triage seat's.

The defect

objectui#8767 landed route C: ObjectGrid now REFUSES a string schema.sort at its fetch path and the query carries no $orderby. The ruling deliberately left the wire shape and the other readers of the same key alone.

One of those readers is the header-arrow parser parseSchemaSort (packages/plugin-grid/src/ObjectGrid.tsx, exported, used at the declaredSort read site). It still parses the retired spellings — "name desc" and ["name desc", ...] — so after objectui#8767:

  • a grid authored sort: "name desc" shows a descending arrow on the name header before anyone clicks, and
  • its query goes out with no ordering at all (plus the retired-spelling console.error).

The arrow therefore states something about the list that is not true of the rows the server returned. That is the exact failure the arrow exists to prevent, stated in the read site's own comment: without the pre-click arrow "the first click on that column would ask for asc on a list that was already desc". Now it is worse in the other direction — the first click asks for asc on a list that is in no declared order.

The probe, already in the tree

packages/plugin-grid/src/__tests__/serverSorting.test.tsx, the case "shows the view's declared sort before anyone clicks", authors sort: 'status desc' and asserts the chevron. It is green after objectui#8767 and was deliberately left authoring the retired spelling, with an annotation, precisely so this state is visible rather than papered over. Reading it beside the same file's "replaces the view's declared sort rather than stacking on it" case — re-authored in the array form by objectui#8767 — shows the two readers disagreeing on one key.

Two comments in ObjectGrid.tsx were updated by objectui#8767 to record the same thing: the parseSchemaSort docblock (which had claimed the fetch path reads all three spellings) and the declaredSort read site (which had claimed the arrow and the wire are the same sort).

Why it was not fixed there

Narrowing parseSchemaSort to the one declared spelling is inside the blast radius objectui#8767's ruling refused: it is exported, pinned, and read together with the export path, and the natural full fix routes the whole key through the shared sink convertSortToQueryParams, which returns a field-to-direction map where ObjectGrid sends a "field order" join string. That is route B on objectui#8767, which the maintainer declined by name on 2026-09-10 as a different card with its own measurement of the server contract and both readers. This is that card.

Appetite (a suggestion, not a ruling)

Whoever takes it should decide as one question, since the answers are coupled: does ObjectGrid keep its private "field order" wire shape or move to the shared sink's map; and does the header reader narrow to the array spelling. A narrowing of parseSchemaSort alone is cheap and closes the visible lie, but it leaves this block as the last private lowering of a key the rest of the platform routes through one sink.

Refs: objectui#8767 (route C, the ruling) · PR #8960 (its implementation) · objectui#8221 (the retirement ruling) · PR #8758 (the shared sink's narrowing)


Generated by Claude Code

Activity

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

    bugSomething isn't workingdomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanedomain: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