diff --git a/.changeset/29395-skills-search-auto-default.md b/.changeset/29395-skills-search-auto-default.md new file mode 100644 index 00000000000..b669604a8e6 --- /dev/null +++ b/.changeset/29395-skills-search-auto-default.md @@ -0,0 +1,12 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): with no `searchableFields` declared, a field marked `searchable: true` joins the default search set + +Clause-②: no +Metadata-protocol: none + +- **Before.** `objectstack-data`, `objectstack-query` and the `objectstack-ui` list-views rule taught the record-search auto-default as type-only: the name field plus the text-like and `select` columns. A field marked `searchable: true` was described as doing nothing, and a `$searchFields` override naming one as refused. +- **Now.** Each says what the platform resolves: with no `searchableFields` declared, the set `search` scans, and the set a `$searchFields` override may name, is the type-based default plus every field marked `searchable: true`, of any type except `secret`, `password`, `vector` or a virtual `formula`. Each also says what does not change: `searchable: false`, the default, never removes a field the type default scans, and declaring `searchableFields` chooses the set. The enum half of the type default is spelled `select` alone, as the resolver has it. +- No schema, key or behaviour changes. diff --git a/skills/objectstack-data/SKILL.md b/skills/objectstack-data/SKILL.md index 5bff0fae402..c4a7d234184 100644 --- a/skills/objectstack-data/SKILL.md +++ b/skills/objectstack-data/SKILL.md @@ -35,7 +35,7 @@ metadata: ## Quick Reference — Detailed Rules -For comprehensive documentation with incorrect/correct examples: +Detailed rules, with incorrect/correct examples: - **[Naming Conventions](./rules/naming.md)** — snake_case rules, option values, config properties - **[Field Types](./rules/field-types.md)** — All field types and configs @@ -85,7 +85,7 @@ the publish door rejects it before that: | `highlightFields` | derived | Ordered field keys used as a record's compact face: the columns a **related list** renders on the parent's detail page, and what a lookup picker shows. Declare it on the CHILD object (see [Relationships](./rules/relationships.md)) | | `namespace` | — | **Not a schema key** — `ObjectSchema.create()` rejects unknown keys, so authoring it is a build error. Embed the prefix directly in `name` instead (e.g. `name: 'crm_account'`) | | `datasource` | `'default'` | Target datasource ID for virtualized data | -| `nameField` | derived (e.g. `'name'`/`'title'`) | **Canonical** record-title field — the stored field used as the record's display name. Use a single text/email field; a composite title to import, sort or search by is a stored computed field (a non-`formula` field with an `expression`, computed and stored on write); a text formula is display-only | +| `nameField` | derived (e.g. `'name'`/`'title'`) | **Canonical** record-title field — the stored field used as the record's display name. Use a single text/email field; a composite title to import, sort or search by is a stored computed field (a non-`formula` field with an `expression`); a text formula is display-only | | `displayNameField` | — | **Deprecated** alias for `nameField` (still honored as a fallback) | | `titleFormat` | — | **Deprecated, not removed (ADR-0079)** — render-only: the server can't return or query it. Use `nameField` (it wins); a composite title follows the `nameField` row | | `enable` | — | Capability flags (trackHistory, searchable, apiEnabled, etc.) | @@ -114,8 +114,10 @@ Toggle system behaviours per object: `searchableFields` on the object is the canonical set `$search` scans (ADR-0061). Leave it unset and search auto-defaults to the `nameField` plus the object's short-text and enum columns (`text` / `email` / `phone` / `url` / `autonumber` / -`textarea` / `markdown` / `select` / `status`); declare it to pin the set -explicitly. Views may narrow it, never widen it. +`textarea` / `markdown` / `select`) plus every field marked `searchable: true`, +of any type except `secret` / `password` / `vector` or a virtual `formula`. `searchable: false`, the +default, never removes a field the type default scans; declare +`searchableFields` to choose the set. Views may narrow it, never widen it. ### ⛔ Searching by a related record's title — mirror the value, always @@ -137,14 +139,14 @@ declare *that* field searchable. A task list searched by project name: fields: { name: { type: 'text', required: true }, project_id: { type: 'lookup', reference: 'project' }, - project_name: { type: 'text', label: 'Project Name' }, // ← the mirror + project_name: { type: 'text', label: 'Project Name' }, }, searchableFields: ['name', 'project_name'], } ``` `?search=apollo` expands to `name $contains 'apollo' OR project_name $contains -'apollo'` — one single-table scan, every driver, no traversal. +'apollo'` — one single-table scan, every driver. ❌ **Never mirror onto a `formula` field.** It is *virtual* (no column to scan) and CEL reads only this record's own fields, so it cannot hold the related title; @@ -154,15 +156,11 @@ a `formula` entry in any `searchableFields` is refused — `os validate` **Mirror maintenance is the trade-off** — a mirror is only as fresh as whatever writes it. Cover both paths: a `beforeInsert` / `beforeUpdate` hook on `task` stamps `project_name` from the incoming `project_id`'s parent; an `afterUpdate` -hook on `project` re-stamps its tasks when it is renamed. Rows written by a path that bypasses hooks (bulk import, direct SQL) need a +hook on `project` re-stamps its tasks when it is renamed. Rows written past the hooks (bulk import, direct SQL) need a one-off backfill. See [Lifecycle Hooks](./references/data-hooks.md). --- -→ Moved verbatim to [references/examples-objects.md](./references/examples-objects.md) § Field Groups (MVP). - ---- - ## Conditional Field Rules Put conditional UI/data-entry rules on the **field definition** when the rule diff --git a/skills/objectstack-query/SKILL.md b/skills/objectstack-query/SKILL.md index 41745bea948..c8b4d54375e 100644 --- a/skills/objectstack-query/SKILL.md +++ b/skills/objectstack-query/SKILL.md @@ -316,11 +316,15 @@ to stored values. protocol normalizes `$searchFields` onto it, and the object form `search: { query, fields }` spells the same narrowing `fields`. -Omit it to search the object's declared `searchableFields` (or an auto-default -of name/title + short-text fields), resolved server-side. It can only **narrow** -that set, never widen it: over the REST/protocol ingress a name outside it is -`400 INVALID_FIELD`, not a silent fall-back to a full scan. The object form -`search: { query, fields }` stays available for the Tier-2 knobs below. +Omit it to search the object's declared `searchableFields` (or, with none +declared, an auto-default: name/title + short-text fields, plus every field +marked `searchable: true` of any type except `secret` / `password` / `vector` or a virtual `formula`), +resolved server-side. It can only **narrow** that set, never widen it: over the +REST/protocol ingress a name outside it is `400 INVALID_FIELD`, not a silent +fall-back to a full scan. `searchable: false`, the default, never removes a +field the type default already scans; declare `searchableFields` to choose the +set. The object form `search: { query, fields }` stays available for the Tier-2 +knobs below. > ⚠️ **Validates, then silently ignored — never emit these.** `fuzzy`, `boost`, > `operator`, `minScore`, `language` and `highlight` are the whole set; their diff --git a/skills/objectstack-ui/rules/list-views.md b/skills/objectstack-ui/rules/list-views.md index 7e50ec9decc..d655fb09a14 100644 --- a/skills/objectstack-ui/rules/list-views.md +++ b/skills/objectstack-ui/rules/list-views.md @@ -185,8 +185,10 @@ export const CaseViews = defineView({ | The object … | The allowed set is | |:-------------|:-------------------| -| declares `searchableFields` | **that list, verbatim** — whatever the field types are | -| declares nothing | the auto-default: the name field + the text-like columns (`text` / `email` / `phone` / `url` / `autonumber` / `textarea` / `markdown` / `select` / `status`) | +| declares `searchableFields` | **that list, verbatim** — any stored field type | +| declares nothing | the auto-default: the name field + the text-like columns (`text` / `email` / `phone` / `url` / `autonumber` / `textarea` / `markdown` / `select`) + every field marked `searchable: true`, of any type except `secret` / `password` / `vector` or a virtual `formula` | + +`searchable: false`, the default, removes nothing the type default scans; declare `searchableFields` to choose the set. Modelling side: **objectstack-data → Search Fields**. Query side (`search.fields` over the API): **objectstack-query → Full-Text Search**. @@ -201,11 +203,9 @@ the whole search box, for every user and every term: no result at all. |:---------------------------|:--------------|:--------------------------| | a subset of the allowed set | clean | scans exactly those columns | | key omitted | clean | scans the object's full allowed set | -| `searchableFields: []` | clean | **identical to omitting it** — empty is *absent* at all three layers, so it scans MORE than `["subject"]` would | -| a renamed / mistyped column | `searchable-field-unknown` | `400 INVALID_FIELD` | -| a dotted path (`account_id.name`) | `searchable-field-unknown` | `400 INVALID_FIELD` | -| a real column outside the allowed set | `searchable-field-unsearchable` | `400 INVALID_FIELD` | -| a virtual `formula` column — nothing stored to scan | `searchable-field-unsearchable` | `400 INVALID_FIELD` | +| `searchableFields: []` | clean | **identical to omitting it** — empty is *absent*, so it scans MORE than `["subject"]` would | +| a renamed / mistyped column, or a dotted path (`account_id.name`) | `searchable-field-unknown` | `400 INVALID_FIELD` | +| a real column outside the allowed set, or a virtual `formula` column | `searchable-field-unsearchable` | `400 INVALID_FIELD` | Both diagnostics are **errors**, not warnings — `os validate` fails the build.