From d7b351bc941a3f83f791a7c1d0097148bb09e816 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 18:30:21 +0000 Subject: [PATCH] docs(spec): wrong-layer guidance for group/hideFields/rowColor on object userActions (#11459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add three curated `guidance` entries to the object-level `userActions` strictObject in packages/spec/src/data/object.zod.ts, mirroring the existing sort/search/filter/editInline wrong-layer wording convention: group, hideFields and rowColor are VIEW `userActions` keys (adopted at #11195) that share a name with nothing on the object block, so an author who learned them on the view writes them here and previously got only the generic unknown-key rejection. Nothing changes about what parses — all three keys were already refused by name on the object block; only the refusal's message gains the same curated pointer the other four wrong-layer keys already carry. Mirrors the existing pin for `sort` with a new test asserting the "VIEW" pointer for all three keys, alongside the pre-existing disjoint-vocabulary check. The #5013 alias-integrity audit judges the three new entries automatically (each is a key the object shape rejects). --- .../object-useractions-view-key-guidance.md | 17 +++++++++++++++++ .../src/data/object-strictness-batch20.test.ts | 12 ++++++++++++ packages/spec/src/data/object.zod.ts | 13 ++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .changeset/object-useractions-view-key-guidance.md diff --git a/.changeset/object-useractions-view-key-guidance.md b/.changeset/object-useractions-view-key-guidance.md new file mode 100644 index 0000000000..ba1a7e37a4 --- /dev/null +++ b/.changeset/object-useractions-view-key-guidance.md @@ -0,0 +1,17 @@ +--- +"@objectstack/spec": patch +--- + +docs(spec): wrong-layer guidance for `group` / `hideFields` / `rowColor` on the object `userActions` block (#11459) + +`data/object.zod.ts`'s object-level `userActions` block already curated +`sort` / `search` / `filter` / `editInline` with a pointer at the VIEW +`userActions` block they actually belong to. `group` / `hideFields` / +`rowColor` — adopted onto the view's vocabulary at #11195 — shared the same +wrong-layer trap but got only the generic unknown-key rejection with an +edit-distance suggestion, which has nothing useful to offer over the object +block's `create`/`import`/`edit`/`delete`/`exportCsv` shape. + +**Nothing changes about what parses.** All three keys were rejected on the +object block before this change and are rejected after it; only the message +gains the same curated pointer the other four wrong-layer keys already carry. diff --git a/packages/spec/src/data/object-strictness-batch20.test.ts b/packages/spec/src/data/object-strictness-batch20.test.ts index 81c42dbe2f..35d1c963cc 100644 --- a/packages/spec/src/data/object-strictness-batch20.test.ts +++ b/packages/spec/src/data/object-strictness-batch20.test.ts @@ -436,6 +436,18 @@ describe('#4001 批 20 — curation is anchored to the sibling contract that mak } }); + it('`userActions.group` / `.hideFields` / `.rowColor` name the VIEW block, same as `sort` (#11459)', () => { + // The three keys adopted onto the view's vocabulary at #11195 got only + // the generic unknown-key rejection on the object block — no curated + // pointer — until this card added one, mirroring `sort`/`search`/ + // `filter`/`editInline` above. + for (const k of ['group', 'hideFields', 'rowColor']) { + const msg = rejectOnObject({ userActions: { [k]: true } }); + expect(msg).toContain('VIEW'); + expect(msg).toContain(k); + } + }); + it('`userActions.clone` points at the `enable` capability block, which really does declare it', () => { const msg = rejectOnObject({ userActions: { clone: false } }); expect(msg).toContain('enable'); diff --git a/packages/spec/src/data/object.zod.ts b/packages/spec/src/data/object.zod.ts index 83246cc9b7..cb00c1b9f1 100644 --- a/packages/spec/src/data/object.zod.ts +++ b/packages/spec/src/data/object.zod.ts @@ -1736,7 +1736,9 @@ const ObjectSchemaBase = strictObject( // disjoint vocabulary (sort/search/filter/refresh/rowHeight/group/ // addRecordForm/editInline/hideFields/rowColor/buttons — the last three // adopted at #11195), so an author who learned that block writes these - // here and gets a shape that has never heard of them. + // here. `group`/`hideFields`/`rowColor` were refused by name but without + // a curated pointer until #11459 gave them one too, mirroring the other + // four. sort: '`sort` is a VIEW `userActions` key, not an object one — the two blocks share a ' + 'name and nothing else. The object block governs CRUD affordances ' + @@ -1752,6 +1754,15 @@ const ObjectSchemaBase = strictObject( '`editInline` is a VIEW `userActions` key. The object-level `edit` flag decides ' + 'whether editing is offered AT ALL; how it is offered (inline vs form) is the ' + "view's call.", + group: + '`group` is a VIEW `userActions` key. This object block governs CRUD affordances ' + + 'only — put toolbar controls on the view that renders the records.', + hideFields: + '`hideFields` is a VIEW `userActions` key. This object block governs CRUD ' + + 'affordances only — put toolbar controls on the view that renders the records.', + rowColor: + '`rowColor` is a VIEW `userActions` key. This object block governs CRUD ' + + 'affordances only — put toolbar controls on the view that renders the records.', clone: '`clone` is a CAPABILITY, not a user action — write `enable: { clone: false }`. ' + 'The `enable` block (ObjectCapabilities) is where record deep-cloning is governed.',