From 627b89191044619f789b6684ce0e386323f82484 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 05:24:43 +0000 Subject: [PATCH 1/5] feat(spec): userActions.hideFields defaults to true (#28912) A list view now offers the Hide Fields control unless it declares `userActions: { hideFields: false }`. The spec default moves from false to true, the ListView read becomes `ua?.hideFields !== false`, and the change is recorded in the default-change ledger. Where the reader's choice is kept (their own list state, or session-only on a system view with no columns) is unchanged. Claude-Session: https://claude.ai/code/session_01ADHkmRSwH9oAqN1JrZZVoR Co-authored-by: Claude --- .changeset/28912-hide-fields-default-on.md | 18 +++++++ packages/spec/scripts/lib/default-changes.ts | 23 ++++++++ packages/spec/src/ui/view.test.ts | 53 +++++++++++++++---- packages/spec/src/ui/view.zod.ts | 19 +++++-- ui/packages/plugin-list/src/ListView.tsx | 16 +++--- .../src/__tests__/ListView.test.tsx | 36 ++++++++++++- 6 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 .changeset/28912-hide-fields-default-on.md diff --git a/.changeset/28912-hide-fields-default-on.md b/.changeset/28912-hide-fields-default-on.md new file mode 100644 index 00000000000..8cf5ba2d8f7 --- /dev/null +++ b/.changeset/28912-hide-fields-default-on.md @@ -0,0 +1,18 @@ +--- +'@objectstack/spec': minor +'@object-ui/plugin-list': minor +--- + +spec(ui): a list view lets readers hide and show columns by default — `userActions.hideFields` defaults to `true` + +Clause-②: yes (widening) +Metadata-protocol: semantic + +A declared default moves. A list view (or a page `interfaceConfig`) whose `userActions` block says nothing about `hideFields` now offers the Hide Fields control, so a reader can remove a column they do not need without the app author having to turn the control on. + +- **Before.** `UserActionsConfigSchema.hideFields` defaulted to `false`, and the list renderer drew the control only for an explicit `hideFields: true`. Generated apps and the CRM example never declared it, so their lists had no way to hide a column. +- **Now.** The default is `true`, and the renderer reads `hideFields !== false`, so the spec default and the renderer agree. A view with no `userActions` block parses with none, and the renderer reads the absent key as the new default. +- **Where the choice is kept** does not change. The control only subtracts from the columns the view draws. On a view that declares `columns`, or a saved view, the choice is stored on the reader's own list state and survives a reload for that reader only. On a system view that declares no `columns`, it lasts for the session. It never writes the shared view. +- **To withhold the control**, write `userActions: { hideFields: false }`. Every authored `hideFields` parses exactly as before, and a non-boolean is still refused. +- **Compiled artifacts.** A document that serialised an earlier parse (an `os compile` artifact) carries the old written `false` and keeps withholding the control. Recompile it, or delete the key. +- The default change is declared in the build's default-change ledger. No key is added or removed. diff --git a/packages/spec/scripts/lib/default-changes.ts b/packages/spec/scripts/lib/default-changes.ts index dc5548974fd..85f55079385 100644 --- a/packages/spec/scripts/lib/default-changes.ts +++ b/packages/spec/scripts/lib/default-changes.ts @@ -716,5 +716,28 @@ export const DEFAULT_CHANGES_BY_MAJOR: Readonly { expect(config.group).toBe(true); expect(config.addRecordForm).toBe(false); expect(config.editInline).toBe(true); - expect(config.hideFields).toBe(false); + expect(config.hideFields).toBe(true); expect(config.rowColor).toBe(false); expect(config.buttons).toBeUndefined(); }); @@ -3495,16 +3495,51 @@ describe('UserActionsConfigSchema', () => { }); }); - // The split is copied from the renderer's reads, not chosen by the spec - // (ruled A on objectui#5435): ListView.tsx computes `showGroup` as - // `ua?.group !== false` (absent ⇒ shown) but `showHideFields` / `showColor` - // as `ua?.hideFields === true` / `ua?.rowColor === true` (absent ⇒ hidden). - // Normalising the three to one default would silently flip a toolbar - // affordance on every view that never wrote the key. - it('pins the group-ON / hideFields-OFF / rowColor-OFF defaults asymmetry, copied from what the renderer reads', () => { + // Maintainer ruling on #28912: 「Fable 评估(核对后) 同意立卡」, confirmed on + // the card 「#28911 和 #28912 都保留,按卡上写的做。」 A reader can hide and + // show the view's columns by default; `hideFields: false` is the opt-out. + // The old OFF default was copied from the renderer's legacy `=== true` read; + // the renderer now reads `ua?.hideFields !== false`, so the two agree again. + describe('hideFields defaults ON (maintainer ruling)', () => { + it('an absent key parses to true', () => { + expect(UserActionsConfigSchema.parse({}).hideFields).toBe(true); + const view = ListViewSchema.parse({ + name: 'accounts', + label: 'Accounts', + columns: [{ field: 'name' }], + userActions: {}, + }); + expect(view.userActions?.hideFields).toBe(true); + }); + + it('an explicit false stays false — the opt-out', () => { + expect(UserActionsConfigSchema.parse({ hideFields: false }).hideFields).toBe(false); + const view = ListViewSchema.parse({ + name: 'accounts', + label: 'Accounts', + columns: [{ field: 'name' }], + userActions: { hideFields: false }, + }); + expect(view.userActions?.hideFields).toBe(false); + }); + + it('a view with no `userActions` block still materialises none', () => { + const view = ListViewSchema.parse({ + name: 'accounts', + label: 'Accounts', + columns: [{ field: 'name' }], + }); + expect(view.userActions).toBeUndefined(); + }); + }); + + // `group` is ON unless turned off (`ua?.group !== false`), `rowColor` is OFF + // unless turned on (`ua?.rowColor === true`) — the renderer's reads, ruled A + // on objectui#5435. `hideFields` left this pair by the ruling above. + it('pins the group-ON / hideFields-ON / rowColor-OFF defaults, matching what the renderer reads', () => { const config = UserActionsConfigSchema.parse({}); expect(config.group).toBe(true); - expect(config.hideFields).toBe(false); + expect(config.hideFields).toBe(true); expect(config.rowColor).toBe(false); }); diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index dae4b859064..443ee9c3152 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -1883,10 +1883,19 @@ export const VisualizationTypeSchema = lazySchema(() => z.enum([ * fold (`SHOW_FLAG_TO_USER_ACTION` in normalize-list-view.ts; ruled Option A on * objectui#5435): the ListView toolbar already honoured all three, but no * spec-valid document could declare them, so the fold's own output failed the - * save gate. The defaults asymmetry is LOAD-BEARING, copied from the renderer's - * reads, not chosen here: `group` defaults ON (`ua?.group !== false`) like the - * other core toolbar controls, while `hideFields` / `rowColor` default OFF - * (`=== true`) — column hiding and row colouring are opt-in affordances. + * save gate. `group` defaults ON (`ua?.group !== false`) like the other core + * toolbar controls, and `rowColor` defaults OFF (`=== true`): row colouring is + * an opt-in affordance. + * + * `hideFields` defaults ON by maintainer ruling on #28912 + * (「Fable 评估(核对后) 同意立卡」, confirmed on the card: + * 「#28911 和 #28912 都保留,按卡上写的做。」): a reader + * can hide and show the view's columns without the author declaring it, and + * `hideFields: false` is the opt-out. The old OFF default was copied from the + * renderer's legacy `=== true` read, not chosen as product; the renderer now + * reads `ua?.hideFields !== false`. The choice stays the reader's own: stored + * on their list state on a view that declares `columns` or a saved view, + * session-only on a system view with no `columns` (objectui#10694). * * `editInline` defaults ON by maintainer ruling (2026-10-10, the v18 line: * 「乙 v18 把 spec 默认翻成 true,editInline: false 变成关法。」): a list view @@ -1918,7 +1927,7 @@ export const UserActionsConfigSchema = lazySchema(() => strictObject({ group: z.boolean().default(true).describe('Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block.'), addRecordForm: z.boolean().default(false).describe('Add records through a form instead of inline'), editInline: z.boolean().default(true).describe('Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field\'s type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place.'), - hideFields: z.boolean().default(false).describe('Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component\'s `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in.'), + hideFields: z.boolean().default(true).describe('Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component\'s `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control.'), rowColor: z.boolean().default(false).describe('Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in.'), buttons: z.array(z.string()).optional().describe('Custom action button IDs to show in the toolbar'), }).describe('User action toggles for the view toolbar')); diff --git a/ui/packages/plugin-list/src/ListView.tsx b/ui/packages/plugin-list/src/ListView.tsx index 983c9f9fe4e..91728613547 100644 --- a/ui/packages/plugin-list/src/ListView.tsx +++ b/ui/packages/plugin-list/src/ListView.tsx @@ -1813,12 +1813,14 @@ export const ListView = React.forwardRef(({ // `show*` flags are folded into it by `normalizeListViewSchema` above, so // there is no dual-read here — one vocabulary, one place. // - // The DEFAULTS are per-toggle and deliberately asymmetric, matching what - // these flags have always done: the four view-shaping affordances plus - // grouping are on unless turned off; column visibility and row coloring are - // off unless turned on. (`hideFields`/`rowColor` default OFF is objectui's - // historical behavior, kept deliberately — flipping it would grow two - // buttons on every existing view.) + // The DEFAULTS are per-toggle and read the spec's own defaults + // (`UserActionsConfigSchema`): the four view-shaping affordances, grouping + // and column visibility are on unless turned off; row coloring is off + // unless turned on. `hideFields` defaults ON by maintainer ruling on #28912 + // — a reader hides and shows columns for themselves, and an app withholds + // the control with `hideFields: false`. Where the choice is stored (the + // reader's own list state, or session-only on a system view with no + // `columns`) is the host's call, not this read's. // // This is the VIEW half of a NAME COLLISION, and the right half here: // `userActions` on a VIEW is toolbar policy (`UserActionsConfigSchema`), @@ -1844,7 +1846,7 @@ export const ListView = React.forwardRef(({ showRefresh: ua?.refresh !== false, showDensity: ua?.rowHeight !== false, showGroup: ua?.group !== false, - showHideFields: ua?.hideFields === true, + showHideFields: ua?.hideFields !== false, showColor: ua?.rowColor === true, // The bundled "Display" form is the default (objectui#12423): the bar // keeps Filter, Sort, Display and Search. `compactToolbar: false` is the diff --git a/ui/packages/plugin-list/src/__tests__/ListView.test.tsx b/ui/packages/plugin-list/src/__tests__/ListView.test.tsx index 0e72135775f..1992351172e 100644 --- a/ui/packages/plugin-list/src/__tests__/ListView.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListView.test.tsx @@ -1753,6 +1753,36 @@ describe('ListView', () => { expect(screen.getByText('Hide fields')).toBeInTheDocument(); }); + // objectstack#28912: `hideFields` defaults ON, matching the spec default + // (`UserActionsConfigSchema`). A view that says nothing offers the + // control; `hideFields: false` withholds it. + it('should show Hide fields when userActions omits hideFields (default ON)', () => { + const schema = { + type: 'list-view', + compactToolbar: false, + objectName: 'contacts', + viewType: 'grid', + columns: ['name', 'email'], + } as unknown as ListViewSchema; + + renderWithProvider(); + expect(screen.getByText('Hide fields')).toBeInTheDocument(); + }); + + it('should not show Hide fields when userActions.hideFields is false', () => { + const schema = { + type: 'list-view', + compactToolbar: false, + objectName: 'contacts', + viewType: 'grid', + columns: ['name', 'email'], + userActions: { hideFields: false }, + } as unknown as ListViewSchema; + + renderWithProvider(); + expect(screen.queryByText('Hide fields')).not.toBeInTheDocument(); + }); + it('should hide Group when userActions.group is false', () => { const schema = { type: 'list-view', @@ -1768,17 +1798,19 @@ describe('ListView', () => { it('should keep the legacy show* flags working — stored views carry them', () => { // Folded by normalizeListViewSchema, so the renderer sees only userActions. + // Since `hideFields` defaults ON (objectstack#28912) a legacy `true` is + // indistinguishable from silence, so the fold is pinned by its `false`. const schema = { type: 'list-view', compactToolbar: false, objectName: 'contacts', viewType: 'grid', columns: ['name', 'email'], - showHideFields: true, + showHideFields: false, } as unknown as ListViewSchema; renderWithProvider(); - expect(screen.getByText('Hide fields')).toBeInTheDocument(); + expect(screen.queryByText('Hide fields')).not.toBeInTheDocument(); }); it('should let userActions win over a conflicting legacy flag', () => { From d9c8c4e8b68df2e6791d122fa8a985482005125a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 05:33:13 +0000 Subject: [PATCH 2/5] chore(spec): regenerate references and authorable defaults for hideFields (#28912) Claude-Session: https://claude.ai/code/session_01ADHkmRSwH9oAqN1JrZZVoR Co-authored-by: Claude --- content/docs/references/ui/page.mdx | 2 +- content/docs/references/ui/view.mdx | 6 +++--- packages/spec/authorable-defaults/ui.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/references/ui/page.mdx b/content/docs/references/ui/page.mdx index fd6124f2cb0..985d5c0b0d6 100644 --- a/content/docs/references/ui/page.mdx +++ b/content/docs/references/ui/page.mdx @@ -185,7 +185,7 @@ View filter rule | **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. | | **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline | | **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. | -| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. | +| **hideFields** | `boolean` | optional (default: `true`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control. | | **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. | | **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar | diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index d0ea8c8c258..9ae054c37e6 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -1090,7 +1090,7 @@ View filter rule | **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. | | **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline | | **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. | -| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. | +| **hideFields** | `boolean` | optional (default: `true`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control. | | **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. | | **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar | @@ -1469,7 +1469,7 @@ View filter rule | **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. | | **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline | | **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. | -| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. | +| **hideFields** | `boolean` | optional (default: `true`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control. | | **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. | | **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar | @@ -1641,7 +1641,7 @@ User action toggles for the view toolbar | **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. | | **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline | | **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. | -| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. | +| **hideFields** | `boolean` | optional (default: `true`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. On by default: a reader hides and shows columns for themselves; declare `hideFields: false` to withhold the control. | | **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. | | **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar | diff --git a/packages/spec/authorable-defaults/ui.json b/packages/spec/authorable-defaults/ui.json index 2f0fae539b7..df19866021e 100644 --- a/packages/spec/authorable-defaults/ui.json +++ b/packages/spec/authorable-defaults/ui.json @@ -106,7 +106,7 @@ "ui/UserActionsConfig:editInline = true", "ui/UserActionsConfig:filter = true", "ui/UserActionsConfig:group = true", - "ui/UserActionsConfig:hideFields = false", + "ui/UserActionsConfig:hideFields = true", "ui/UserActionsConfig:refresh = true", "ui/UserActionsConfig:rowColor = false", "ui/UserActionsConfig:rowHeight = true", From 5e51cfd42020ba1409ad6a528b528016f5b87587 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 05:33:51 +0000 Subject: [PATCH 3/5] docs(spec): say an interface page keeps its closed-by-default hideFields (#28912) Claude-Session: https://claude.ai/code/session_01ADHkmRSwH9oAqN1JrZZVoR Co-authored-by: Claude --- .changeset/28912-hide-fields-default-on.md | 5 +++-- packages/spec/scripts/lib/default-changes.ts | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.changeset/28912-hide-fields-default-on.md b/.changeset/28912-hide-fields-default-on.md index 8cf5ba2d8f7..5c6bb990718 100644 --- a/.changeset/28912-hide-fields-default-on.md +++ b/.changeset/28912-hide-fields-default-on.md @@ -8,10 +8,11 @@ spec(ui): a list view lets readers hide and show columns by default — `userAct Clause-②: yes (widening) Metadata-protocol: semantic -A declared default moves. A list view (or a page `interfaceConfig`) whose `userActions` block says nothing about `hideFields` now offers the Hide Fields control, so a reader can remove a column they do not need without the app author having to turn the control on. +A declared default moves. A list view whose `userActions` block says nothing about `hideFields` now offers the Hide Fields control, so a reader can remove a column they do not need without the app author having to turn the control on. - **Before.** `UserActionsConfigSchema.hideFields` defaulted to `false`, and the list renderer drew the control only for an explicit `hideFields: true`. Generated apps and the CRM example never declared it, so their lists had no way to hide a column. -- **Now.** The default is `true`, and the renderer reads `hideFields !== false`, so the spec default and the renderer agree. A view with no `userActions` block parses with none, and the renderer reads the absent key as the new default. +- **Now.** The default is `true`, and the list renderer reads `hideFields !== false`, so the spec default and the renderer agree. A view with no `userActions` block parses with none, and the renderer reads the absent key as the new default. +- **Interface pages.** A page `interfaceConfig.userActions` block that omits the key parses to `true` as well, but an interface page keeps its own closed-by-default toolbar, as it already does for `filter`, `rowHeight` and `group`: it draws the control only for an explicit `hideFields: true`. - **Where the choice is kept** does not change. The control only subtracts from the columns the view draws. On a view that declares `columns`, or a saved view, the choice is stored on the reader's own list state and survives a reload for that reader only. On a system view that declares no `columns`, it lasts for the session. It never writes the shared view. - **To withhold the control**, write `userActions: { hideFields: false }`. Every authored `hideFields` parses exactly as before, and a non-boolean is still refused. - **Compiled artifacts.** A document that serialised an earlier parse (an `os compile` artifact) carries the old written `false` and keeps withholding the control. Recompile it, or delete the key. diff --git a/packages/spec/scripts/lib/default-changes.ts b/packages/spec/scripts/lib/default-changes.ts index 85f55079385..3c170c8a6d8 100644 --- a/packages/spec/scripts/lib/default-changes.ts +++ b/packages/spec/scripts/lib/default-changes.ts @@ -726,10 +726,13 @@ export const DEFAULT_CHANGES_BY_MAJOR: Readonly Date: Mon, 2 Nov 2026 07:05:47 +0000 Subject: [PATCH 4/5] feat(spec): D3 semantic entry for the hideFields default flip (#28912) Adds `list-view-hide-fields-default-on` so `os migrate meta` and the upgrade guide carry the flip, points the ledger row, the schema comment and the changeset at it, and corrects the stale "hideFields default OFF" comment in ui core's normalize-list-view. Claude-Session: https://claude.ai/code/session_01ADHkmRSwH9oAqN1JrZZVoR Co-authored-by: Claude --- .changeset/28912-hide-fields-default-on.md | 2 +- packages/spec/scripts/lib/default-changes.ts | 2 +- .../18.list-view-hide-fields-default-on.ts | 56 +++++++++++++++++++ packages/spec/src/ui/view.zod.ts | 3 +- .../core/src/utils/normalize-list-view.ts | 5 +- 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 packages/spec/src/migrations/entries/semantic/18.list-view-hide-fields-default-on.ts diff --git a/.changeset/28912-hide-fields-default-on.md b/.changeset/28912-hide-fields-default-on.md index 5c6bb990718..b49f2f7d853 100644 --- a/.changeset/28912-hide-fields-default-on.md +++ b/.changeset/28912-hide-fields-default-on.md @@ -16,4 +16,4 @@ A declared default moves. A list view whose `userActions` block says nothing abo - **Where the choice is kept** does not change. The control only subtracts from the columns the view draws. On a view that declares `columns`, or a saved view, the choice is stored on the reader's own list state and survives a reload for that reader only. On a system view that declares no `columns`, it lasts for the session. It never writes the shared view. - **To withhold the control**, write `userActions: { hideFields: false }`. Every authored `hideFields` parses exactly as before, and a non-boolean is still refused. - **Compiled artifacts.** A document that serialised an earlier parse (an `os compile` artifact) carries the old written `false` and keeps withholding the control. Recompile it, or delete the key. -- The default change is declared in the build's default-change ledger. No key is added or removed. +- **Upgrade path.** The ADR-0087 D3 semantic entry `list-view-hide-fields-default-on` carries the change to `os migrate meta` and the upgrade guide. The default change is declared in the build's default-change ledger. No key is added or removed. diff --git a/packages/spec/scripts/lib/default-changes.ts b/packages/spec/scripts/lib/default-changes.ts index 3c170c8a6d8..5c1b0c60ad5 100644 --- a/packages/spec/scripts/lib/default-changes.ts +++ b/packages/spec/scripts/lib/default-changes.ts @@ -740,7 +740,7 @@ export const DEFAULT_CHANGES_BY_MAJOR: Readonly z.enum([ * renderer's legacy `=== true` read, not chosen as product; the renderer now * reads `ua?.hideFields !== false`. The choice stays the reader's own: stored * on their list state on a view that declares `columns` or a saved view, - * session-only on a system view with no `columns` (objectui#10694). + * session-only on a system view with no `columns` (objectui#10694). D3 entry: + * `list-view-hide-fields-default-on` (protocol 18). * * `editInline` defaults ON by maintainer ruling (2026-10-10, the v18 line: * 「乙 v18 把 spec 默认翻成 true,editInline: false 变成关法。」): a list view diff --git a/ui/packages/core/src/utils/normalize-list-view.ts b/ui/packages/core/src/utils/normalize-list-view.ts index 2f29bf8989b..863ff47fcf3 100644 --- a/ui/packages/core/src/utils/normalize-list-view.ts +++ b/ui/packages/core/src/utils/normalize-list-view.ts @@ -397,8 +397,9 @@ function readListViewKind(value: unknown): string | undefined { * per-flag, so a view may carry `userActions` for some toggles and a legacy * flag for others. NOTE the fold does not apply any default: an absent flag * stays absent, because the defaults are per-toggle (search/sort/filter/ - * rowHeight/group default ON, hideFields/rowColor default OFF) and belong to - * the renderer, not to the vocabulary bridge. + * rowHeight/group/hideFields default ON, rowColor default OFF — the spec's + * own defaults, `hideFields` ON since objectstack#28912) and belong to the + * renderer, not to the vocabulary bridge. * - the view's `inlineEdit` → `userActions.editInline` (objectui#5144), with * the value carried over and an explicit `editInline` winning. `inlineEdit` * stays on the result, because `ListView` seeds the grid's edit mode from From 5e1742ee6760b057f0dbdbcb87f3e8a78fae0393 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 07:16:32 +0000 Subject: [PATCH 5/5] test(ui-core): the fold's default-polarity pin reads hideFields ON (#28912) Claude-Session: https://claude.ai/code/session_01ADHkmRSwH9oAqN1JrZZVoR Co-authored-by: Claude --- .../normalize-list-view.foldOutputAuthorable-5435.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/packages/core/src/utils/__tests__/normalize-list-view.foldOutputAuthorable-5435.test.ts b/ui/packages/core/src/utils/__tests__/normalize-list-view.foldOutputAuthorable-5435.test.ts index 1ca86944a11..3798970cc2c 100644 --- a/ui/packages/core/src/utils/__tests__/normalize-list-view.foldOutputAuthorable-5435.test.ts +++ b/ui/packages/core/src/utils/__tests__/normalize-list-view.foldOutputAuthorable-5435.test.ts @@ -206,8 +206,9 @@ describe('the fold`s output is authorable (objectui#5435)', () => { // `showGroup: ua?.group !== false` — default ON expect(defaults.group).toBe(true); - // `showHideFields: ua?.hideFields === true` — default OFF (opt-in) - expect(defaults.hideFields).toBe(false); + // `showHideFields: ua?.hideFields !== false` — default ON since + // objectstack#28912 (the maintainer flipped both sides together) + expect(defaults.hideFields).toBe(true); // `showColor: ua?.rowColor === true` — default OFF (opt-in) expect(defaults.rowColor).toBe(false); });