diff --git a/.changeset/studio-searchable-toggle.md b/.changeset/studio-searchable-toggle.md new file mode 100644 index 0000000000..d7f56e3883 --- /dev/null +++ b/.changeset/studio-searchable-toggle.md @@ -0,0 +1,13 @@ +--- +"@object-ui/app-shell": minor +--- + +feat(studio): surface the `enable.searchable` toggle in ObjectSettingsPanel (#2800) + +`enable.searchable` was corrected to LIVE during framework#2377 (the Global +Search executor gates on it — explicit `false` opts the object out of +cross-object search), making it the only live `enable.*` flag the Studio +settings panel did not expose. It now renders as an opt-out toggle (default +on) alongside feeds/activities/clone, with en + zh labels that point +field-level match configuration at `searchableFields` (ADR-0061) to avoid +conflating the two. diff --git a/packages/app-shell/src/views/metadata-admin/i18n.ts b/packages/app-shell/src/views/metadata-admin/i18n.ts index e5a2f4d1db..a3252c4d3f 100644 --- a/packages/app-shell/src/views/metadata-admin/i18n.ts +++ b/packages/app-shell/src/views/metadata-admin/i18n.ts @@ -1356,6 +1356,9 @@ const ENGINE_STRINGS_EN: Record = { 'engine.studio.settings.capActivities': 'Activity timeline (activities)', 'engine.studio.settings.capActivitiesDesc': 'Mirrors create/update/delete into the record timeline. Off = no timeline rows for this object (the compliance audit log is unaffected).', + 'engine.studio.settings.capSearchable': 'Global search (searchable)', + 'engine.studio.settings.capSearchableDesc': + "Include this object's records in cross-object global search. Off = records no longer appear in global search results. Which fields match is configured per-object via searchableFields, not here.", 'engine.studio.settings.capClone': 'Cloning (clone)', 'engine.studio.settings.capCloneDesc': 'Allow duplicating records from the record page / API. Off = the clone endpoint returns 403 for this object.', @@ -2887,6 +2890,9 @@ const ENGINE_STRINGS_ZH: Record = { 'engine.studio.settings.capActivities': '活动时间线(activities)', 'engine.studio.settings.capActivitiesDesc': '将创建/更新/删除镜像到记录时间线。关闭后该对象不再产生时间线记录(合规审计日志不受影响)。', + 'engine.studio.settings.capSearchable': '全局搜索(searchable)', + 'engine.studio.settings.capSearchableDesc': + '将该对象的记录纳入跨对象全局搜索。关闭后记录不再出现在全局搜索结果中。匹配哪些字段由对象的 searchableFields 配置,与此开关无关。', 'engine.studio.settings.capClone': '克隆(clone)', 'engine.studio.settings.capCloneDesc': '允许从记录页/API 复制记录。关闭后克隆接口对该对象返回 403。', diff --git a/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.test.tsx b/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.test.tsx index d78906c2e0..f9cada2d49 100644 --- a/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.test.tsx +++ b/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.test.tsx @@ -104,19 +104,19 @@ describe('ObjectSettingsPanel — capabilities (enable.*, framework#2707/#2727)' // …opt-out flags default ON. expect((screen.getByTestId('cap-feeds') as HTMLInputElement).checked).toBe(true); expect((screen.getByTestId('cap-activities') as HTMLInputElement).checked).toBe(true); + expect((screen.getByTestId('cap-searchable') as HTMLInputElement).checked).toBe(true); expect((screen.getByTestId('cap-clone') as HTMLInputElement).checked).toBe(true); // trash/mru no longer exist in the spec (removed, framework#2377 — a - // rendered toggle would author a key the strict schema rejects); - // searchable is live but not yet surfaced here. - expect(screen.queryByTestId('cap-searchable')).toBeNull(); + // rendered toggle would author a key the strict schema rejects). expect(screen.queryByTestId('cap-trash')).toBeNull(); expect(screen.queryByTestId('cap-mru')).toBeNull(); }); it('reflects authored values (explicit false on an opt-out, true on an opt-in)', () => { - renderPanel({ ...baseDraft, enable: { feeds: false, files: true } }); + renderPanel({ ...baseDraft, enable: { feeds: false, files: true, searchable: false } }); expect((screen.getByTestId('cap-feeds') as HTMLInputElement).checked).toBe(false); expect((screen.getByTestId('cap-files') as HTMLInputElement).checked).toBe(true); + expect((screen.getByTestId('cap-searchable') as HTMLInputElement).checked).toBe(false); // Untouched flags keep their effective defaults. expect((screen.getByTestId('cap-activities') as HTMLInputElement).checked).toBe(true); }); diff --git a/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.tsx b/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.tsx index 58954ee8c4..e42fe5be29 100644 --- a/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.tsx +++ b/packages/app-shell/src/views/studio-design/ObjectSettingsPanel.tsx @@ -31,8 +31,7 @@ * are opt-IN (spec default false), feeds/activities/clone are opt-OUT * (spec default true, explicit false disables). `trash`/`mru` were * REMOVED from the spec (framework#2377 — ObjectCapabilities is now - * .strict(), authoring them is a parse error); `searchable` is LIVE - * (global-search opt-out) but not yet surfaced here. + * .strict(), authoring them is a parse error). */ import React from 'react'; @@ -87,8 +86,8 @@ export function ObjectSettingsPanel({ // Capabilities (`enable.*`, framework#2707/#2727). Checked = the flag's // EFFECTIVE runtime value; toggling writes an explicit boolean into the // enable block (preserving sibling keys). trackHistory/files are opt-in - // (=== true enables); feeds/activities/clone are opt-out (only an explicit - // false disables). + // (=== true enables); searchable/feeds/activities/clone are opt-out (only + // an explicit false disables). const enable = (draft.enable && typeof draft.enable === 'object' ? draft.enable : {}) as Record; const patchEnable = (key: string, value: boolean) => onPatch({ enable: { ...enable, [key]: value } }); const CAPABILITIES: Array<{ key: string; optIn: boolean; labelKey: string; descKey: string }> = [ @@ -96,6 +95,7 @@ export function ObjectSettingsPanel({ { key: 'files', optIn: true, labelKey: 'engine.studio.settings.capFiles', descKey: 'engine.studio.settings.capFilesDesc' }, { key: 'feeds', optIn: false, labelKey: 'engine.studio.settings.capFeeds', descKey: 'engine.studio.settings.capFeedsDesc' }, { key: 'activities', optIn: false, labelKey: 'engine.studio.settings.capActivities', descKey: 'engine.studio.settings.capActivitiesDesc' }, + { key: 'searchable', optIn: false, labelKey: 'engine.studio.settings.capSearchable', descKey: 'engine.studio.settings.capSearchableDesc' }, { key: 'clone', optIn: false, labelKey: 'engine.studio.settings.capClone', descKey: 'engine.studio.settings.capCloneDesc' }, ];