Skip to content

Commit 255588b

Browse files
docs(ui): the searchableFields boundary is allowed-set membership, not field type (#6897) (#6922)
views.mdx:106 said a lookup in a view's `searchableFields` is always refused. Measured at both layers, that is false: the boundary is membership in the object's server-resolved allowed set, and field TYPE is consulted only on the auto-default branch (the object declares nothing). On an object declaring `searchableFields: ['subject', 'account_id']`, a view narrowing to the lookup `account_id` is ACCEPTED and scanned, while a `text` column the object left out is REFUSED — the exact inverse of a type-based reading. An author following the old row would delete a narrowing that works. The row now states the set-membership rule and links to a new `### Toolbar search (searchableFields)` section that mirrors the terminology landed in skills/objectstack-ui/SKILL.md by PR #6898, so the two corpora agree. The dotted-path half of the old row was correct and is kept. Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn Co-authored-by: Claude <noreply@anthropic.com>
1 parent 59e9b7c commit 255588b

1 file changed

Lines changed: 48 additions & 1 deletion

File tree

‎content/docs/ui/views.mdx‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ A List View controls how a collection of records is presented. It supports multi
103103
| `data` | `ViewData` | optional | Data source configuration (defaults to the `object` provider) |
104104
| `filter` | `array` | optional | Base filter criteria |
105105
| `sort` | `array` | optional | Sort configuration |
106-
| `searchableFields` | `string[]` | optional | Fields the toolbar search scans — **narrows** the object's set, never widens it (ADR-0061). Entries must be the object's **own** columns: a lookup (`project_id`) or a dotted path (`project_id.name`) is refused, and every toolbar search on the list then returns `400 INVALID_FIELD` (#4254). To search by a related record's title, [mirror it into a stored field](/docs/data-modeling/schema-design#searching-by-a-related-records-title--mirror-the-value) on the object and list that |
106+
| `searchableFields` | `string[]` | optional | Fields the toolbar search scans — **narrows** the set the object allows, never widens it (ADR-0061). Every entry must be in that allowed set, or every toolbar search on the list returns `400 INVALID_FIELD` (#4254) — see [Toolbar search](#toolbar-search-searchablefields) below |
107107
| `grouping` | `object` | optional | Row grouping configuration |
108108
| `pagination` | `object` | optional | Pagination settings |
109109
| `selection` | `object` | optional | Row selection mode |
@@ -118,6 +118,53 @@ The view's machine name is its **key** in the container (`listViews.urgent` on
118118
object `task` becomes `task.urgent`); the default `list` claims `task.default`.
119119
List and form views share that one namespace — don't reuse a key.
120120

121+
### Toolbar search (`searchableFields`)
122+
123+
The toolbar's search box scans a set the **object** owns. A list view's
124+
`searchableFields` **narrows** that set for this one list — it can never widen
125+
it, and the runtime enforces that by **refusing the request**, not by quietly
126+
dropping the extra name (ADR-0061, #4254).
127+
128+
**What the object allows** is resolved server-side, and it is the whole rule:
129+
130+
| The object … | The allowed set is |
131+
| :--- | :--- |
132+
| declares `searchableFields` | **that list, verbatim** — whatever the field types are |
133+
| declares nothing | the auto-default: the name field + the text-like columns (`text` / `email` / `phone` / `url` / `autonumber` / `textarea` / `markdown` / `select` / `status`) |
134+
135+
So field **type** decides only in the second row. On an object that declares
136+
`searchableFields: ['subject', 'account_id']`, a view narrowing to
137+
`['account_id']` — a lookup — is **accepted** and scanned (a `$contains` over
138+
the stored id: narrow, but the engine executes it); on that same object,
139+
narrowing to a `text` column the object left out is **refused**. Judge every
140+
entry against the object's allowed set, never against the type list.
141+
142+
A **dotted path** (`account_id.name`) is not a valid entry on either branch —
143+
`search` scans this object's own columns, and the narrowing is intersected with
144+
the allowed set by exact name. To search by a related record's title,
145+
[mirror it into a stored field](/docs/data-modeling/schema-design#searching-by-a-related-records-title--mirror-the-value)
146+
on the object and list that.
147+
148+
<Callout type="warn">
149+
**One bad entry `400`s EVERY search on that list.** Clients echo this
150+
declaration verbatim as the `$searchFields` override — the active view's list
151+
wins over the object's — and the ingress gate refuses any entry outside the
152+
allowed set before the engine ever runs. The blast radius is the list's whole
153+
search box, for every user and every term: not a narrower result, no result at
154+
all.
155+
</Callout>
156+
157+
| What you write on the view | `os validate` | Toolbar search at runtime |
158+
| :--- | :--- | :--- |
159+
| a subset of the allowed set | clean | scans exactly those columns |
160+
| key omitted, or `searchableFields: []` | clean | scans the object's full allowed set |
161+
| a renamed / mistyped column, or a dotted path | `searchable-field-unknown` | `400 INVALID_FIELD` |
162+
| a real column outside the allowed set | `searchable-field-unsearchable` | `400 INVALID_FIELD` |
163+
164+
Both diagnostics are **errors**, not warnings — `os validate` fails the build.
165+
The object's own set, and the stored-mirror prescription, are covered under
166+
[Global search](/docs/data-modeling/schema-design#global-search--searchable--searchablefields).
167+
121168
### Column Configuration
122169

123170
{/* os:check */}

0 commit comments

Comments
 (0)