Skip to content

ObjectViewProps.views[].sort declares direction, but every sort consumer reads order — a host-supplied desc view silently sorts ascending #5293

Description

@os-support-ai

Found while implementing #5270 (the named-view sort arity fix). Filed unassigned, not claiming. Deliberately NOT fixed there#5270's ruling is the arity of the slot the view sort is written into, and this is a different mechanism (a key name that no reader knows) whose correct fix is not mechanical. See "Why this needs adjudication" below.

Verified on origin/main @ 12841b617.

The declaration

packages/plugin-view/src/ObjectView.tsx:206-214 — the host-composition views prop:

 views?: Array< {
id: string;
label: string;
type: ViewType;
columns?: string[];
sort?: Array< { field: string; direction: 'asc' | 'desc' } >;
filter?: any[];
[key: string]: any;
} >;

Note direction. Every other sort surface in the repo spells the same field order:

surfacespelling
SortConfig (packages/types/src/objectql.ts:212)order
NamedListView.sort (:1579)order
ObjectGridSchema.sort / .defaultSort (:578 / :673)order
QuerySortEntry — the shared sink (packages/core/src/utils/sort-query.ts)order
ObjectViewProps.views[].sortdirection

Nothing reads direction

activeView is the resolved views entry (ObjectView.tsx:551) and its sort reaches exactly three consumers, none of which knows the word:

Counter-probed: grep -n direction packages/plugin-view/src/ObjectView.tsx returns only the declaration at :211, an unrelated prose use at :342, and <Drawer direction="right"> at :1158 — while viewSort in the same file returns 3 hits, so the zero is a reading and not a bad grep.

Consequence

A host that composes views={[{ id, label, type: 'grid', sort: [{ field: 'created_at', direction: 'desc' }] }]} — the exact shape the prop declares — gets an ascending list, and (on the grid path) an ascending arrow above it. There is no failure signal: the declaration says the value is well-formed, and the direction is dropped at three independent readers rather than rejected at one.

The spelling is live, not theoretical: packages/plugin-view/src/index.tsx:179 registers sort: [{ field: 'name', direction: 'asc' }] as a defaultProps example, and packages/app-shell/src/views/view-config-adapter.test.ts round-trips { field, direction } fixtures through the studio adapter (which itself is spelling-agnostic — sort?: unknown[]).

Distinct from SortUI, which legitimately owns direction on its ownSortUISchema and converts at its boundaries (SortUI.tsx:54 / :71). This issue is only about the views prop.

Why this needs adjudication rather than a one-line fix

#4869 already ruled on this exact class, for defaultSort:

defaultSort 实际存在 sink 不认的拼法,正确的动作是先裁定该拼法算不算已声明的 authoring surface(要么进 sink 的契约,要么在 producer 端拒绝),而不是在 ObjectView 里加一层容忍。

So the two candidate routes are a decision, not a refactor:

  1. direction is a declared authoring surface → it belongs in the sink's contract (convertSortToQueryParams), and ObjectView translates at the prop boundary. Cost: a third accepted spelling repo-wide, against the convergence [finding] ObjectView 是最后一处不走共享 sort sink 的读点 —— $orderby: sort 原样透传,归一化外包给了某一个 adapter #4869 is driving.
  2. direction is a declaration bug → the prop becomes order, matching every other sort surface, and the two in-repo direction producers listed above are corrected. Cost: a breaking change to a public prop's shape (objectui ships its own breaks as minor per AGENTS.md §版本号策略), and any external host spelling direction starts failing loudly instead of quietly.

Route 2 is the one that makes AI-authored composition hard to get wrong — one spelling, declared = enforced — but it is a public-surface break and should not be taken by a dev agent on its own authority.

Dedup

Searched open issues (views sort direction, ObjectViewProps sort, sort direction spelling, plus the file path). Nothing on this. Related: #4869 (the sort-sink convergence card, which names this class), #5270 (the arity of the grid slot, same expression, different mechanism), #5269 (the table.columns forwarding gap, same file).

Blocked-by: #4869 — the sink's contract is the thing route 1 would extend, so that card's shape should settle first.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions