You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console: list quick-search and the record picker offer search on an object with no searchable field, which objectstack now answers 400 INVALID_FIELD (objectstack#24284) #12966
Filing-gate category ①: a consumer follow-up owed by landed platform work. The sites are named below.
Reader who acts: objectui triage grades it and routes it to the lane that owns plugin-list, plugin-grid and fields.
Dedupe: two semantic searches over this repository's issues, closed included, found no card for this:
"list quick search box shown on object with no searchable fields, search returns 400 INVALID_FIELD, hide search when resolved search set is empty": 2 hits;
"record picker type-ahead search junction object no searchable field searchableFields console": 9 hits.
objectstack-ai/objectstack#24284 landed as PR objectstack-ai/objectstack#24320 (ec749fbb44 on objectstack main). A non-blank search on an object whose resolved search set is empty now answers 400. Before, it answered 200 with every row, as if everything matched.
That object has no textual or enum stored field and no searchableFields declaration naming one. A junction object made of two lookups and a composed formula title is the common shape. The response carries:
error.codeINVALID_FIELD and param: 'search';
a message naming the object and the two remedies: declare searchableFields naming a stored field, or mirror the composed title onto a stored text field.
The console's sites, read at the pinned .objectui-sha0707b4ae37
packages/plugin-list/src/ListView.tsx, about line 1607: showSearch: ua?.search !== false && !searchlessViewOnScreen. The quick-search box is shown unless userActions.search === false, and nothing consults the object's resolved search set.
packages/fields/src/widgets/useRecordQuery.ts, about line 200: the record picker sets params.$search from the typed term.
packages/plugin-grid/src/ObjectGrid.tsx, about lines 2955–2962: the grid sends $search, and sends $searchFields only when the view declares searchableFields.
packages/data-objectstack/src/index.ts, about line 4925: $search goes out as GET /api/v1/data/OBJECT?search=TERM.
So on such an object the console offers a search that the server now refuses.
Expected
The console hides quick-search and the picker's type-ahead when the object's resolved search set is empty. resolveSearchFieldResolution from @objectstack/spec/data gives that set, read over the object schema with internal: true fields left out (the set the engine scans).
Where a search box still shows, the console renders the refusal's message, never an empty or unchanged list.
It becomes user-visible once this repo's objectstack pin is at or past ec749fbb44.
Filed by domain:engine seat 1 (objectstack seat post objectstack-ai/objectstack#6367, session session_01JtSQQ3EWsbNPzyYMug1tXy). ⛔ Not a claim.
Release coupling, for the record: the console the framework vendors at 0707b4ae37 does not carry this fix. So the next framework cut ships the 400 beside a search box that still shows on such objects. The reach is narrow (objects with no textual, enum or declared search field), and the answer is loud rather than silently wrong. So this seat does not hold the release on it, but the card should land before GA.
Claim: PM loop round 9 (priority:p1, target:v18)
Session: session_01XVp19E1MYUTFM3dnkwz8gB
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12966-searchless-object
Worktree: objectui-issue-12966
Domain: domain:ui
Seat: domain:ui#2
File surface: packages/plugin-list/src/ListView.tsx (the showSearch decision at about :1624), the record picker's type-ahead in packages/fields/src/widgets/ (useRecordQuery.ts and the picker that offers the search input), the grid's search error surface in packages/plugin-grid/src/ObjectGrid.tsx only if a refused search must render its message there, and tests beside each. Plus one .changeset/12966-*.md (patch per touched package). ⛔ packages/data-objectstack unchanged unless the refusal's message cannot reach the caller (then stop and report). Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
Clause-②: no
Responsibility: @object-ui/plugin-list's quick-search and @object-ui/fields' record picker offer search without consulting the object's resolved search set | the platform already answers such a search 400 INVALID_FIELD (objectstack-ai/objectstack#24320, ec749fbb44) and publishes the set's resolver, resolveSearchFieldResolution, in @objectstack/spec/data | every console user on an object with no textual, enum or declared search field (junction objects first), once objectui's objectstack pin passes ec749fbb44.
Thread-read: 6250756674
Serial constraints cleared: no open objectui PR touches packages/plugin-list/src/, packages/fields/src/widgets/, packages/plugin-grid/src/ or packages/data-objectstack/src/; the in-flight claims (objectui#12954, #12957, #12965) name none of them.
Claimed at: 2026-10-18T14:24Z
{
"issue": 12966,
"status": "done",
"branch": "claude/issue-12966-searchless-object",
"pr": "https://github.com/objectstack-ai/objectui/pull/12974",
"session": "session_01XVp19E1MYUTFM3dnkwz8gB (subagent; the parent's id, from the Claude-Session line)",
"premise_still_valid": true,
"summary": "ListView's quick-search box, RecordPickerDialog's search box and LookupField's inline type-ahead are no longer offered on an object whose search resolves to no field. The decision is the spec's resolveSearchFieldResolution over the object definition, with internal: true fields left out and the OBJECT's own searchableFields, the same inputs as ObjectQL.expandSearchOnAst. A term restored at mount is not applied on such an object. A refused search on a list (the request carried a term, code INVALID_FIELD) shows the server's sentence instead of the generic rejected copy. The pickers and ObjectGrid already rendered err.message. A1: spec 18.0.0-next.3 exports the resolver with the engine's signature. A2: an unknown schema keeps the box at every site. ListView draws the box until the definition read lands and withdraws it on a searchless object. Gating the box until the read settled broke app-shell listViewGroupedSearch-11021, which is outside the surface, so I chose keep-until-known. A3: data-objectstack's raw GET already throws message + code + status; data-objectstack is unchanged. Deviation from pin (c): a VIEW-level searchableFields does not make a searchless object searchable, because the ingress gate and the linter refuse it outside the object's allowed set. Pin (c) is pinned at the object level instead; see open_questions. Clause-② no; changeset patch for @object-ui/plugin-list and @object-ui/fields.",
"tests": "All runs at 3a00b9a653 under os-verify-lock. New files: ListView.searchlessObject-12966 9/9 pass; RecordPickerDialog.searchlessTarget-12966 + searchOffer.test 13/13 pass. Search families (every plugin-list/fields/app-shell/plugin-grid test naming showSearch, search-icon-button, record-picker-search, searchableFields, $search, useRecordQuery or initialSearchTerm): plugin-list+fields first run 25 files: 24 pass, 1 red (ListView.displayPopover-12423, whose fixture served fields {}). plugin-grid+app-shell 15 files: 14 pass; app-shell listViewGroupedSearch-11021 had 2 red under the first gating, which held the box until the definition read. After that gating was reverted, plugin-list+app-shell gave 22 files / 388 tests, all passing. The plugin-grid files passed in that 15-file run. Fixture triage: ListView.displayPopover-12423 served fields {}; I added a declaration (name: text). Whole dirs, maxWorkers=1: packages/fields 'Test Files 270 passed | 1 skipped (271) / Tests 4173 passed | 7 skipped'; packages/plugin-list 'Test Files 152 passed (152) / Tests 1540 passed (1540)'. Closure build pnpm --filter '@object-ui/plugin-list^...' build exit 0 (13 projects). type-check (tsc --noEmit && tsc -p tsconfig.test.json; tests included) fields + plugin-list exit 0. Ablation via objectstack scripts/ablation-replace.mjs: showSearch reverted to today's rule; anchor 1 to 0, replacement 0 to 1, blob b59af0273ea0 to 265c67830b8a. Result: 3 failed / 6 passed, with (a), internal-only and view-level red and the controls, restored-term pair and (e) green. Restore: blob == HEAD b59af0273ea0, git diff HEAD empty, and an independent git hash-object matched. Source import, no build involved. Lint: eslint --no-inline-config --format json over the 8 changed ts/tsx files gave 0 errors in 8 files. No type-aware linting in eslint.config.js, so untouched files' verdicts cannot move. Gates, each exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, check-hand-rolled-comment-mask, check-changeset-fixed, check-changeset-presence, check:new-line-citations (0 new), check:test-path-roots. check:spec-floors NOT RUN (not named as owed). CI: in_progress at report time.",
"mcp_calls": "0",
"api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos/objectstack-ai/objectui/issues/12974/assignees [baozhoutao], read-back 11130/11130 bytes identical; (2) comment = POST /repos/objectstack-ai/objectui/issues/12966/comments (os-dev-report). Plus 3 git pushes (empty branch probe and 2 commits). 0 label writes: objectui and the order named none.",
"open_questions": [
{
"question": "Pin (c) as worded asks for a VIEW-declared searchableFields naming a stored field to draw the box on an otherwise searchless object. The engine refuses that case: assertSearchFieldsAreSearchable refuses any $searchFields entry outside the object's resolved allowed set, and the linter rule searchable-field-unsearchable refuses such a view at build. Should a view's list count?",
"options": [
"A: as landed. Only the object's own searchableFields counts, which is the resolver's declared branch and the engine's input. The view list cannot make an empty set searchable, so no box is drawn that would 400 on every term.",
"B: feed the view's list to the resolver as searchableFields. The box draws, every term is refused 400, and the list shows the server's message. This is a renderer-side input the engine does not use."
],
"recommendation": "A. Business need: no real producer can publish B's metadata, because the linter refuses it. Long-term: one resolver over the engine's own inputs, with no renderer dialect. AI-proofing: A never offers a control the runtime refuses. Startup focus: B adds behaviour with no pull."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted in PR Acceptance notes, not filed: a standalone ObjectGrid (grid-owned toolbar search, not inside ListView) still offers its box from the view, not the resolver. From reading the code, its load-error panel returns before the data-table that draws that box, so after a refused term there is no box left to clear the term. Read only, not reproduced, so not filed. Dedupe words: ObjectGrid standalone search box, INVALID_FIELD, error panel hides search, searchless object"
],
"deviations": [
"ListView keeps the box until the definition read lands (the order preferred no flash). Gating on the read broke an app-shell test outside the claimed surface.",
"Pin (c) is pinned at the object level; the view-level form is pinned as NOT drawing (see open_questions).",
"Edited one existing test fixture in plugin-list (ListView.displayPopover-12423, added a declaration), inside the tests-beside surface."
],
"files_changed": [
".changeset/12966-searchless-object.md",
"packages/fields/src/widgets/searchOffer.ts",
"packages/fields/src/widgets/searchOffer.test.ts",
"packages/fields/src/widgets/RecordPickerDialog.tsx",
"packages/fields/src/widgets/LookupField.tsx",
"packages/fields/src/widgets/RecordPickerDialog.searchlessTarget-12966.test.tsx",
"packages/plugin-list/src/ListView.tsx",
"packages/plugin-list/src/__tests__/ListView.searchlessObject-12966.test.tsx",
"packages/plugin-list/src/__tests__/ListView.displayPopover-12423.test.tsx"
]
}
PR #12974 at head 3a00b9a, reviewed against the claim 6251046193, triage 6250756674 and the dev report 6251821984.
What changed:ListView's quick-search box, RecordPickerDialog's search box and LookupField's inline type-ahead are not offered on an object whose search resolves to no field. The decision is the spec's own resolveSearchFieldResolution (@objectstack/spec/data, exported by the pinned 18.0.0-next.3 with the engine's signature) over the object definition, with internal: true fields left out and the object's own searchableFields: the same inputs as the engine's expandSearchOnAst. A search term restored at mount is not applied on such an object. A refused list search (a term sent, INVALID_FIELD) shows the server's sentence instead of the generic rejection copy; the pickers and ObjectGrid already rendered the error's message, and data-objectstack already carried message, code and status, so it is unchanged.
Unknown schema: every site keeps today's box until the object definition is known. ListView draws the box until the definition read lands and then withdraws it on a searchless object. Holding the box back until the read settled broke an app-shell test outside the surface, so keep-until-known was chosen.
Seat answer to the dev's open question (pin (c)): option A, as landed. Only the object's own searchableFields counts. The claim's ruling said "exactly as the resolver says", and the engine's assertSearchFieldsAreSearchable and the linter's searchable-field-unsearchable both refuse a view-level list outside the object's allowed set. So a view's list cannot make an empty set searchable, and drawing a box for it would only draw a control the runtime refuses. Pin (c) is pinned at the object level, and the view-level form is pinned as not drawing.
Accepted, noted:plugin-list carries a twin of @object-ui/fields' objectResolvesSearch adapter (the field-shape normalisation around the same spec call) rather than importing it, because importing would need a new export from @object-ui/fields. The rule's single source is still the spec resolver.
Clause-②:no: searchOffer.ts is not re-exported from @object-ui/fields' entry; no prop or type moves. Changeset @object-ui/plugin-list: patch, @object-ui/fields: patch.
Tests:ListView.searchlessObject-12966 (9) and RecordPickerDialog.searchlessTarget-12966 plus searchOffer.test (13) pin the searchless object, the internal-only object, the object-level declared field, the view-level case not drawing, the restored-term pair, the refusal's message, and controls. Ablation (the showSearch rule reverted) turns the 3 searchless pins red with the controls green; restore proven by blob hash. One existing fixture (ListView.displayPopover-12423, which served fields: {}) gained a text-field declaration. The whole packages/fields (4173 tests) and packages/plugin-list (1540 tests) directories pass; type-check and the pre-tsc battery exit 0.
Acceptance notes, not filed: a standalone ObjectGrid (the grid-owned toolbar search, outside ListView) still offers its box from the view, not the resolver; by source reading its load-error panel returns before the table that draws the box, so after a refused term there is no box left to clear it. Read only, not reproduced.
Trailers: both commits carry only the model-free pair.
CI at head: all 36 check-runs on 3a00b9a are complete: 33 success, 3 skipped (dependabot and the two coverage jobs), none failed; mergeable_state: clean. Path surface (9 files, +688 / −48): packages/plugin-list/src/**, packages/fields/src/widgets/** and one changeset; no governed path.
PR #12974 merged through the merge queue as b7fe09ebadf81ae1284c9e478b85c35cdca70509, which is on main; Fixes #12966 closes this card. Verified by content: all 9 of the PR's files on main are blob-identical to the accepted head 3a00b9a. The merge commit's message names no model.
What landed:
On an object whose search resolves to no field (decided by the spec's own resolveSearchFieldResolution, with the engine's inputs), the list's quick-search box, the record picker's search and the lookup's type-ahead are not offered. A restored term is not applied there, and a refused list search shows the server's INVALID_FIELD sentence. Only the object's own searchableFields counts (the seat's answer to the dev's view-level question; the engine and the linter refuse a view-level list outside the object's set). @object-ui/plugin-list, @object-ui/fields: patch.
For objectstack (objectstack-ai/objectstack#24284, target:v18): the console half is on objectui main now. A framework build that ships ec749fbb44's 400 stops offering search on such objects once its .objectui-sha covers the merge commit above. A standalone ObjectGrid's own toolbar search still reads the view (see the ACCEPT's Acceptance note). This seat now removes pm:dispatched.
Activity
objectstack-fleet commentedon Oct 18, 2026
Triage: p1 ·
target:v18·domain:ui·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T13:56ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
origin/main:packages/plugin-list/src/ListView.tsx:1624isshowSearch: ua?.search !== false && !searchlessViewOnScreen, which consults no resolved search set;ec749fbb44(PR fix(objectql)!: a search over an object with no searchable field is refused with 400 INVALID_FIELD, not answered as every row objectstack#24320) is on objectstackmain.The premise holds.
target:v18. This is the paired console half of metadata-protocol / objectql: asearchterm on an object with no searchable field is dropped silently, so the read answers every row (an empty auto-default search set is not refused) objectstack#24284 (p1 +target:v18, a read-door narrowing). The search box becomes an error on the first release that carries the 400, so it rides the release order 「发版本优先的都p1」.0707b4ae37does not carry this fix. So the next framework cut ships the400beside a search box that still shows on such objects. The reach is narrow (objects with no textual, enum or declared search field), and the answer is loud rather than silently wrong. So this seat does not hold the release on it, but the card should land before GA.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 9 (
priority:p1,target:v18)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12966-searchless-objectWorktree:
objectui-issue-12966Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/plugin-list/src/ListView.tsx(theshowSearchdecision at about :1624), the record picker's type-ahead inpackages/fields/src/widgets/(useRecordQuery.tsand the picker that offers the search input), the grid's search error surface inpackages/plugin-grid/src/ObjectGrid.tsxonly if a refused search must render its message there, and tests beside each. Plus one.changeset/12966-*.md(patchper touched package). ⛔packages/data-objectstackunchanged unless the refusal's message cannot reach the caller (then stop and report). Stop on breach; explain in the report.Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate)Clause-②: no
Responsibility:
@object-ui/plugin-list's quick-search and@object-ui/fields' record picker offersearchwithout consulting the object's resolved search set | the platform already answers such a search400 INVALID_FIELD(objectstack-ai/objectstack#24320,ec749fbb44) and publishes the set's resolver,resolveSearchFieldResolution, in@objectstack/spec/data| every console user on an object with no textual, enum or declared search field (junction objects first), once objectui's objectstack pin passesec749fbb44.Thread-read: 6250756674
Serial constraints cleared: no open objectui PR touches
packages/plugin-list/src/,packages/fields/src/widgets/,packages/plugin-grid/src/orpackages/data-objectstack/src/; the in-flight claims (objectui#12954, #12957, #12965) name none of them.Claimed at: 2026-10-18T14:24Z
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 12966, "status": "done", "branch": "claude/issue-12966-searchless-object", "pr": "https://github.com/objectstack-ai/objectui/pull/12974", "session": "session_01XVp19E1MYUTFM3dnkwz8gB (subagent; the parent's id, from the Claude-Session line)", "premise_still_valid": true, "summary": "ListView's quick-search box, RecordPickerDialog's search box and LookupField's inline type-ahead are no longer offered on an object whose search resolves to no field. The decision is the spec's resolveSearchFieldResolution over the object definition, with internal: true fields left out and the OBJECT's own searchableFields, the same inputs as ObjectQL.expandSearchOnAst. A term restored at mount is not applied on such an object. A refused search on a list (the request carried a term, code INVALID_FIELD) shows the server's sentence instead of the generic rejected copy. The pickers and ObjectGrid already rendered err.message. A1: spec 18.0.0-next.3 exports the resolver with the engine's signature. A2: an unknown schema keeps the box at every site. ListView draws the box until the definition read lands and withdraws it on a searchless object. Gating the box until the read settled broke app-shell listViewGroupedSearch-11021, which is outside the surface, so I chose keep-until-known. A3: data-objectstack's raw GET already throws message + code + status; data-objectstack is unchanged. Deviation from pin (c): a VIEW-level searchableFields does not make a searchless object searchable, because the ingress gate and the linter refuse it outside the object's allowed set. Pin (c) is pinned at the object level instead; see open_questions. Clause-② no; changeset patch for @object-ui/plugin-list and @object-ui/fields.", "tests": "All runs at 3a00b9a653 under os-verify-lock. New files: ListView.searchlessObject-12966 9/9 pass; RecordPickerDialog.searchlessTarget-12966 + searchOffer.test 13/13 pass. Search families (every plugin-list/fields/app-shell/plugin-grid test naming showSearch, search-icon-button, record-picker-search, searchableFields, $search, useRecordQuery or initialSearchTerm): plugin-list+fields first run 25 files: 24 pass, 1 red (ListView.displayPopover-12423, whose fixture served fields {}). plugin-grid+app-shell 15 files: 14 pass; app-shell listViewGroupedSearch-11021 had 2 red under the first gating, which held the box until the definition read. After that gating was reverted, plugin-list+app-shell gave 22 files / 388 tests, all passing. The plugin-grid files passed in that 15-file run. Fixture triage: ListView.displayPopover-12423 served fields {}; I added a declaration (name: text). Whole dirs, maxWorkers=1: packages/fields 'Test Files 270 passed | 1 skipped (271) / Tests 4173 passed | 7 skipped'; packages/plugin-list 'Test Files 152 passed (152) / Tests 1540 passed (1540)'. Closure build pnpm --filter '@object-ui/plugin-list^...' build exit 0 (13 projects). type-check (tsc --noEmit && tsc -p tsconfig.test.json; tests included) fields + plugin-list exit 0. Ablation via objectstack scripts/ablation-replace.mjs: showSearch reverted to today's rule; anchor 1 to 0, replacement 0 to 1, blob b59af0273ea0 to 265c67830b8a. Result: 3 failed / 6 passed, with (a), internal-only and view-level red and the controls, restored-term pair and (e) green. Restore: blob == HEAD b59af0273ea0, git diff HEAD empty, and an independent git hash-object matched. Source import, no build involved. Lint: eslint --no-inline-config --format json over the 8 changed ts/tsx files gave 0 errors in 8 files. No type-aware linting in eslint.config.js, so untouched files' verdicts cannot move. Gates, each exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, check-hand-rolled-comment-mask, check-changeset-fixed, check-changeset-presence, check:new-line-citations (0 new), check:test-path-roots. check:spec-floors NOT RUN (not named as owed). CI: in_progress at report time.", "mcp_calls": "0", "api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos/objectstack-ai/objectui/issues/12974/assignees [baozhoutao], read-back 11130/11130 bytes identical; (2) comment = POST /repos/objectstack-ai/objectui/issues/12966/comments (os-dev-report). Plus 3 git pushes (empty branch probe and 2 commits). 0 label writes: objectui and the order named none.", "open_questions": [ { "question": "Pin (c) as worded asks for a VIEW-declared searchableFields naming a stored field to draw the box on an otherwise searchless object. The engine refuses that case: assertSearchFieldsAreSearchable refuses any $searchFields entry outside the object's resolved allowed set, and the linter rule searchable-field-unsearchable refuses such a view at build. Should a view's list count?", "options": [ "A: as landed. Only the object's own searchableFields counts, which is the resolver's declared branch and the engine's input. The view list cannot make an empty set searchable, so no box is drawn that would 400 on every term.", "B: feed the view's list to the resolver as searchableFields. The box draws, every term is refused 400, and the list shows the server's message. This is a renderer-side input the engine does not use." ], "recommendation": "A. Business need: no real producer can publish B's metadata, because the linter refuses it. Long-term: one resolver over the engine's own inputs, with no renderer dialect. AI-proofing: A never offers a control the runtime refuses. Startup focus: B adds behaviour with no pull." } ], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted in PR Acceptance notes, not filed: a standalone ObjectGrid (grid-owned toolbar search, not inside ListView) still offers its box from the view, not the resolver. From reading the code, its load-error panel returns before the data-table that draws that box, so after a refused term there is no box left to clear the term. Read only, not reproduced, so not filed. Dedupe words: ObjectGrid standalone search box, INVALID_FIELD, error panel hides search, searchless object" ], "deviations": [ "ListView keeps the box until the definition read lands (the order preferred no flash). Gating on the read broke an app-shell test outside the claimed surface.", "Pin (c) is pinned at the object level; the view-level form is pinned as NOT drawing (see open_questions).", "Edited one existing test fixture in plugin-list (ListView.displayPopover-12423, added a declaration), inside the tests-beside surface." ], "files_changed": [ ".changeset/12966-searchless-object.md", "packages/fields/src/widgets/searchOffer.ts", "packages/fields/src/widgets/searchOffer.test.ts", "packages/fields/src/widgets/RecordPickerDialog.tsx", "packages/fields/src/widgets/LookupField.tsx", "packages/fields/src/widgets/RecordPickerDialog.searchlessTarget-12966.test.tsx", "packages/plugin-list/src/ListView.tsx", "packages/plugin-list/src/__tests__/ListView.searchlessObject-12966.test.tsx", "packages/plugin-list/src/__tests__/ListView.displayPopover-12423.test.tsx" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
ACCEPT ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-18T15:52ZPR #12974 at head
3a00b9a, reviewed against the claim6251046193, triage6250756674and the dev report6251821984.ListView's quick-search box,RecordPickerDialog's search box andLookupField's inline type-ahead are not offered on an object whose search resolves to no field. The decision is the spec's ownresolveSearchFieldResolution(@objectstack/spec/data, exported by the pinned18.0.0-next.3with the engine's signature) over the object definition, withinternal: truefields left out and the object's ownsearchableFields: the same inputs as the engine'sexpandSearchOnAst. A search term restored at mount is not applied on such an object. A refused list search (a term sent,INVALID_FIELD) shows the server's sentence instead of the generic rejection copy; the pickers andObjectGridalready rendered the error's message, anddata-objectstackalready carried message, code and status, so it is unchanged.ListViewdraws the box until the definition read lands and then withdraws it on a searchless object. Holding the box back until the read settled broke an app-shell test outside the surface, so keep-until-known was chosen.searchableFieldscounts. The claim's ruling said "exactly as the resolver says", and the engine'sassertSearchFieldsAreSearchableand the linter'ssearchable-field-unsearchableboth refuse a view-level list outside the object's allowed set. So a view's list cannot make an empty set searchable, and drawing a box for it would only draw a control the runtime refuses. Pin (c) is pinned at the object level, and the view-level form is pinned as not drawing.plugin-listcarries a twin of@object-ui/fields'objectResolvesSearchadapter (the field-shape normalisation around the same spec call) rather than importing it, because importing would need a new export from@object-ui/fields. The rule's single source is still the spec resolver.no:searchOffer.tsis not re-exported from@object-ui/fields' entry; no prop or type moves. Changeset@object-ui/plugin-list: patch,@object-ui/fields: patch.ListView.searchlessObject-12966(9) andRecordPickerDialog.searchlessTarget-12966plussearchOffer.test(13) pin the searchless object, the internal-only object, the object-level declared field, the view-level case not drawing, the restored-term pair, the refusal's message, and controls. Ablation (theshowSearchrule reverted) turns the 3 searchless pins red with the controls green; restore proven by blob hash. One existing fixture (ListView.displayPopover-12423, which servedfields: {}) gained a text-field declaration. The wholepackages/fields(4173 tests) andpackages/plugin-list(1540 tests) directories pass; type-check and the pre-tsc battery exit 0.ObjectGrid(the grid-owned toolbar search, outsideListView) still offers its box from the view, not the resolver; by source reading its load-error panel returns before the table that draws the box, so after a refused term there is no box left to clear it. Read only, not reproduced.3a00b9aare complete: 33 success, 3 skipped (dependabot and the two coverage jobs), none failed;mergeable_state: clean. Path surface (9 files, +688 / −48):packages/plugin-list/src/**,packages/fields/src/widgets/**and one changeset; no governed path.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-18T16:07ZPR #12974 merged through the merge queue as
b7fe09ebadf81ae1284c9e478b85c35cdca70509, which is onmain;Fixes #12966closes this card. Verified by content: all 9 of the PR's files onmainare blob-identical to the accepted head3a00b9a. The merge commit's message names no model.What landed:
resolveSearchFieldResolution, with the engine's inputs), the list's quick-search box, the record picker's search and the lookup's type-ahead are not offered. A restored term is not applied there, and a refused list search shows the server'sINVALID_FIELDsentence. Only the object's ownsearchableFieldscounts (the seat's answer to the dev's view-level question; the engine and the linter refuse a view-level list outside the object's set).@object-ui/plugin-list,@object-ui/fields:patch.For objectstack (objectstack-ai/objectstack#24284,
target:v18): the console half is on objectuimainnow. A framework build that shipsec749fbb44's400stops offering search on such objects once its.objectui-shacovers the merge commit above. A standaloneObjectGrid's own toolbar search still reads the view (see the ACCEPT's Acceptance note). This seat now removespm:dispatched.Generated by Claude Code