Skip to content

A view's declared sort naming a formula field clears lint, then 400s on every load — the SORT axis has no authoring gate, unlike SEARCH (#6674) #9257

Description

@yinlianghui

Filed from objectui#3950's implementation (objectui PR #4965). Recorded, not claimed.

The gap

packages/lint/src/validate-searchable-fields.ts rejects a searchableFields entry naming a formula field at authoring time, at error level. Its own docblock states the reason in the general form:

once the REST read path validates that override against the object (#4254), a stale declaration the engine had been silently skipping becomes a 400 INVALID_FIELD on every list search for that object — a request-time break whose cause is an authoring typo made long before.

The SORT axis now has exactly that shape and no such rule. After #6994 (ingress, assertSortFieldsExist) and #7095 (engine, assertOrderByIsMaterializable), an orderBy naming a formula field is a hard 400 INVALID_SORT. But nothing reads a view's sort declaration at authoring time:

Net effect: a list view authored with sort: 'expected_revenue desc' (a formula field) validates, publishes, and reports valid — then fails on first load, every load, with a 400 the author cannot connect to the declaration. That is strictly worse than the search-axis case the existing rule gates, because a refused sort is the view's initial fetch, not one optional interaction.

Why this is not objectui's to fix, and why it is not covered by objectui#3950

objectui#3950 / PR #4965 closes the affordance half in the renderer: the column header no longer offers a sort on a formula column. It deliberately does not filter a DECLARED sort out of the outgoing query — silently dropping an author's declaration would hide the authoring error, which is the fallback-in-the-consumer move AGENTS.md #0.1 forbids. So the loud runtime failure is the intended behaviour on that side, and it is intended precisely because the producer is supposed to reject it first. Right now nothing does.

The sort picker in objectui's list toolbar also keeps such a field listed when the current sort already names it — that row is the only way for a user to REMOVE the offending sort. So the runtime is already built assuming the declaration can exist and must be fixable; the missing piece is refusing it where it is written.

Suggested shape

A lint rule mirroring validate-searchable-fields, judging by the same spec predicate the other three doors read (isVirtualSearchField / SEARCH_VIRTUAL_TYPES from @objectstack/spec/data — the storage fact, pinned to formula alone), over every author-written sort position:

Deliberately NOT COMPUTED_VALUE_TYPESsummary and autonumber have real stored columns and sort correctly; the trap is pinned by name in the engine's own conformance suite.

Level is the open question worth deciding rather than guessing: error matches the search-axis precedent and the consequence here is larger (the view's first fetch), but it would newly fail lint for metadata that exists today and currently only fails at runtime.

Not verified by me

Whether any shipped example app or corpus view already declares such a sort. objectui#3950's own corpus sweep found formula fields RENDERED as columns (crm_opportunity.expected_revenue, showcase project.budget_remaining) but did not find one named in a sort declaration — so this may be a gate with no current violations, which is the cheap moment to add it.

Refs: #6994 (ingress refusal), #7095 (engine refusal), #6674 (the SEARCH axis' twin, with the lint rule), #8296 (the FILTER axis), objectui#3950 / objectui PR #4965 (the renderer-affordance half).

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions