Skip to content

Commit b5b928a

Browse files
feat(types,components)!: retire data-table selectionStyle and chatbot floatingConfig on both faces, file data-table's inline-edit flags by name (objectui#6152, round 5) (#11320)
Refs #6152 Clause-②: yes (narrowing). `selectionStyle` and the base-face `floatingConfig` retire on both faces, and `editable` / `singleClickEdit` leave the authored docs, under the startup no-gradualism rule. It is priced in the changeset. Round 5 of objectui#6152, dispatched under claim `5923137359` (`domain:devx` seat 2, session `session_01TdiauJaVCHuj45EzZGUxHh`, on the maintainer's direct order). The rulings are the seat review `5921390451`: its three answers to round 4's open questions. ⛔ This is a draft from a dispatched dev: do not mark it ready and do not enable auto-merge. The card stays open: `ObjectGridSchema`'s keys and the spec-derived pairs (objectui#2231) are not this round's. ## What changed 1. **`DataTableSchema.editable` / `singleClickEdit`: answer 1, B.** Both keys are filed by name on `RuntimeOnlyNamedAllowList`, each with its reason, under round 4's class rule: a key a host produces in code and no document authors (the `showAddRow` precedent). No declaration moved. `content/docs/components/complex/data-table.mdx` no longer teaches them on an authored node. The interface block drops them, and the `editable: true` example becomes an "Inline editing" section. That section says the capability is host-paired: the table only stages an edit, and `object-grid` sets both keys on the table it builds and supplies the save path. It points a document author at `object-grid`. The two members' doc comments say the same. 2. **`DataTableSchema.selectionStyle`: answer 2, B.** It is retired on both faces: a `?: never` tombstone on `data-display.ts`, and a `retirementTombstone()` on `zod/data-display.zod.ts` whose message says to delete the key and that `selectable` alone turns selection on. The hover-only checkbox branch in `packages/components/src/renderers/complex/data-table.tsx` is dropped, so the `'always'` behaviour is the only one. 3. **Base-face `ChatbotSchema.floatingConfig`: answer 3, A.** It is retired on the `chatbot` face only: `?: never` on `complex.ts` and a `retirementTombstone()` on `zod/complex.zod.ts`, whose message names `type: "chatbot-floating"`. - **Mechanism, from the measurement:** a base tombstone only, with no re-declaration. `ChatbotFloatingSchema` does NOT inherit the base member. Its TS face is `BaseSchema` plus `Pick` of `ChatbotSchema` over `ChatbotSharedKey` and `maxToolRoundtrips`, plus its own `floatingConfig` member. Its zod twin is `BaseSchema.extend` with the shared pick spread and its own `floatingConfig` arm. So the floating registration's declaration, mirrored in round 4, was already separate on both faces and is untouched. - It is a tombstone and not a deletion because `ChatbotSchema` is a `BaseSchema` carrier, where a deleted member reads as `any` through the index signature. The discriminator is cited, not restated, so `tombstone-discriminator-agreement-9684.test.ts` still counts four statements. - The chatbot half of `floating-chatbot-trigger-icon-retired.test.ts` is flipped, not deleted. On a `chatbot` node it now asserts the refusal of the whole `floatingConfig` key on both faces, a live config included, with a no-`floatingConfig` control. `chatbot-registration-authoring-faces-7655.test.ts`'s type pins follow. 4. **Ledger.** `zod-mirror-parity.test.ts`: the `UnmirroredDeclared`, `RuntimeOnlyDeclared` and `RuntimeOnlyNamedAllowList` blocks, and every prose total reconciled to the parser count. One non-vacuity leg was reshaped; see the acceptance notes. 5. **Docs.** `content/docs/plugins/plugin-chatbot.mdx` no longer says `floatingConfig` type-checks and parses on a `chatbot` node. 6. **Changeset.** `.changeset/6152-unmirrored-round5.md` bumps `@object-ui/types` and `@object-ui/components` to minor, with the break stated. Three pending entries that this round makes false get dated, append-only `**Correction, 2026-10-01**` notes, with frontmatter and every prior line intact: `6152-unmirrored-round4.md`, `7654-floating-chatbot-trigger-icon-tombstone.md` and `7655-chatbot-registration-authoring-faces.md`. ## Per key | key | node | ruling | route | TS face | zod face | renderer | docs | |---|---|---|---|---|---|---|---| | `editable` | `data-table` | B | filed by name, runtime-only (host-paired) | unchanged (`boolean`), doc comment notes the pairing | unchanged (not mirrored) | unchanged | removed from the authored interface block; section rewritten | | `singleClickEdit` | `data-table` | B | filed by name, runtime-only (host-paired) | unchanged (`boolean`), doc comment notes the pairing | unchanged (not mirrored) | unchanged | as above | | `selectionStyle` | `data-table` | B | retired (ADR-0049) | `?: never` | `retirementTombstone()` | hover branch dropped | none taught it | | `floatingConfig` | `chatbot` | A | retired (ADR-0049) | `?: never` | `retirementTombstone()` | none (never read) | `plugin-chatbot.mdx` corrected | | `floatingConfig` | `chatbot-floating` | (kept) | none | unchanged | unchanged (round 4's arm) | unchanged | unchanged | ## Ledger totals, by TypeScript-parser count of `zod-mirror-parity.test.ts` | ledger (entries / keys) | before, base `3d09948` | after, head `c1b5767` | |---|---|---| | `KnownDrift` | 49 / 86 | 49 / 86 | | `UnmirroredDeclared` | 6 / 20 (3 / 14 spec-derived, 3 / 6 local) | 5 / 16 (3 / 14 spec-derived, 2 / 2 local) | | `RuntimeOnlyDeclared` | 4 / 27 | 4 / 29 | | `RuntimeOnlyNamedAllowList` | 3 / 20 | 3 / 22 | | `WiderThanDeclared` | 3 / 3 | 3 / 3 | | `MirroredUndeclared` | 2 / 2 | 2 / 2 | | shared entries / union of the two unmirrored ledgers | 1 of 4 / 9 (6 + 3) | 0 of 4 / 9 (5 + 4) | ⚠️ **The local half is 2 / 2, not the 0 / 0 the dispatch predicted (hypothesis H1 falsified).** The two keys left are `displayMode` on `ChatbotSchema` and on `ChatbotFloatingSchema`. Under objectui#7654 maintainer ruling B, kept by round 4's Q3 B, their refusal stays TypeScript-only: stored designer documents carry `displayMode: 'floating'`. So they stay in `UnmirroredDeclared` by decision, not as mirroring debt. The ledger prose says so, and the non-vacuity pin "the LOCAL half read as EMPTY" still holds. After this round, `UnmirroredDeclared` holds only the spec-derived 3 / 14 (objectui#2231) and those two decided entries. ## What was measured, before editing (base `3d09948`) - **Type-checker read and producer census.** The round-4 instrument: the TS compiler API over every `packages/*/src` and `apps/*/src`, 5585 files. - `ChatbotSchema.floatingConfig`: 0 reads, 0 producers, 1 test producer (the tripwire). - `ChatbotFloatingSchema.floatingConfig`: 1 read (the floating registration). - `DataTableSchema.selectionStyle`: 1 read (the `data-table` destructure) and 0 producers. - `editable` / `singleClickEdit`: 1 read each (the destructure) and 1 producer each (`ObjectGrid`). - Lit controls: `ChatbotSchema.messages` 3 reads, `maxHeight` 2, `DataTableSchema.columns` 2 reads and 2 producers. - **Authored census in this repository.** 493 tracked JSON files, 354 Markdown JSON fences and 3202 TS/TSX files with a literal `type`. - `data-table`: `selectionStyle` 0, and `editable` / `singleClickEdit` 1 each, both `ObjectGrid` in code with no document. - `chatbot`: `floatingConfig` 1, a test. - Lit controls: `data-table.columns` 71, `selectable` 8, `chatbot-floating.floatingConfig` 4. - **objectstack census.** `git grep -i` on a self-named ref of objectstack `origin/main` (`9b0de7de7`): `selectionStyle`, `selection_style` and `floatingConfig` 0 hits. The lit control on the same ref was `singleClickEdit`, with 6 hits, all on the spec's `ObjectGridProps`, not `data-table`. **There is no named user of hover-style selection, so the stop condition did not fire.** - **Runtime probe, a one-off.** The real `SchemaRenderer` and registry, varying one key. Both files were deleted after each run, leaving `git status` and `git diff HEAD` empty. - At base, a selectable `data-table` with `selectionStyle: 'hover'` differed from unset: 6651 against 6481 characters. The checkbox cell gained `relative`, and each checkbox was wrapped in a `transition-opacity opacity-0 group-hover/row:opacity-100` div. `'always'` was byte-equal to unset, and the `showRowNumbers` control moved the DOM. - At head, `'hover'` is byte-equal to unset (6481 = 6481), with no hover wrapper. - `chatbot` with against without a full `floatingConfig`: byte-equal at base and at head (2600 = 2600). The `placeholder` control moved the DOM, and the same config on `chatbot-floating` drew its title. ## Verification on head `c1b5767` Every heavy run went through `bash /home/user/objectstack/scripts/pm/os-verify-lock.sh` with `OS_VERIFY_LOCK_SLOT=issue-6152`, `NODE_OPTIONS=--max-old-space-size=4096` and a memory watchdog; the lowest available memory was 11244 MB. - **Builds.** `pnpm exec turbo run build` over `check-doc-snippet-types --build-filter` with `--concurrency=2`: "Tasks: 35 successful, 35 total". `@object-ui/types` reports "dist completeness: 1 package(s) complete (138 emitted files verified)". - **Type-checks.** `pnpm --filter X run type-check` exits 0 with 0 TS errors for `@object-ui/types` (`tsc --noEmit`, `-p tsconfig.examples.json`, `-p tsconfig.test.json`), `components`, `plugin-chatbot` and `plugin-grid`. The last three are the packages the type-checker census named. - **Tests,** all with `pnpm exec vitest run --maxWorkers=4`: - `packages/types/`: 306 passed (306), 7701 tests passed; - `packages/components/` in two runs: 192 passed and 1 skipped (193), with 1920 tests passed; then 156 passed (156), with 1560 tests passed; - the consumer set: 309 passed (309), 4939 tests passed. It covers `plugin-chatbot`, `plugin-grid`, `examples/schema-catalog`, `packages/cli`, the three app-shell files importing `@object-ui/types/zod` and `app-shell/.../metadata-admin/previews/__tests__/`. - **Gates, each exit 0.** - Changesets: `check:changeset-claims`, report-only, with 47 pending entries naming a touched file; three were re-read and corrected as above. Also `check:pending-changeset-literals`, `check-changeset-presence` ("9 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"), `changeset:check` and `check-changeset-overwrite`, report-only, whose findings are the 3 dated appends with frontmatter unchanged. - Docs: `check:doc-fences`, `check:doc-snippets` ("698 of 698 block(s) judged, 0 failed"), `docs:check-links` ("Links are valid across 17 scan roots"), `check:doc-examples`, `check:doc-types`, `check:doc-example-ids`, `check:doc-example-readers` and `check:skill-examples`. - Others: `check:control-bytes`, `check:new-line-citations` ("0 new citation(s)"), `check:handler-key-reads`, `check:component-surface-parity` (report-only), `check:designer-field-key-parity`, `check:element-data-source-declaration`, `check:prompt-keys`, `check:readme-exports`, `check:spec-symbols`, `check:test-path-roots`, `check:unreferenced-sources` and `check-type-check-coverage`. - `check-governed-queue-guard --test` over the 15 paths: NOT GOVERNED. - **Narrowed lint against the merge base `cd5b19a`.** The 9 lintable changed files ran through `eslint --format json`. - With inline config: 0 errors and 75 warnings. - With `--no-inline-config`: 1 error and 77 warnings. The one error is the `react-hooks/static-components` line in `data-table.tsx`'s row-action menu, inline-disabled at an untouched line, and it is identical at base. Per-file error and warning counts are equal at base and head; the new test file reads 0 / 0. - The output judged 9 files and ignored 0. - Invariance: `eslint.config.js` declares no `parserOptions` (so no type-aware project), and no rule in `eslint-rules/` reads the disk, so the diff cannot move an untouched file's verdict. The repo-wide lint is CI's. ## Ablations, predictions written first, each restored to blob == HEAD with `git diff HEAD` empty Each ran through `ablation-replace.mjs`, with its anchor count proven 1 → 0 on disk and restored. - **A1** deleted the `selectionStyle` zod arm. The round-5 pin file read "5 failed | 9 passed (14)", exactly the five predicted `selectionStyle` rows. `tsc -p tsconfig.test.json` exited 2 with one error, at `assertionUnmirroredMatchesLedger`. - **A2** restored `ChatbotSchema.floatingConfig` to `FloatingChatbotConfig`. vitest stayed green on the three chatbot pin files (43 passed), since the change is type-level. `tsc` exited 2 with exactly the four predicted errors: TS2578 in the round-5 pin file and in the trigger-icon tripwire, TS2344 in the 7655 pin file, and one at `assertionDriftMatchesLedger`. - **A3** deleted the `editable` allow-list row. `tsc` exited 2 with exactly one TS2344, at `assertionRuntimeOnlyIsCallbackShapedOrNamed`, and the parity file's vitest stayed 37 passed, as predicted. ## Acceptance notes - **One non-vacuity leg was reshaped, not dropped.** The leg asserted that the two unmirrored ledgers share at least one entry. This round made them disjoint for real: `DataTableSchema`, the last pair in both, left `UnmirroredDeclared`. Keeping the leg would have forced a key back onto a ledger. It is replaced by a lit control on the same reader: `KnownDrift` shares a pair with each unmirrored ledger, so the header's `0` is a reading, not a name-normalisation artefact. The reasoning is in the test's own comment. - **Merged `origin/main` twice, never rebased:** `de4459a`, then `c1b5767`. The second merge followed PR #11310, which edits `complex.ts`. The ledger counts did not move with either merge. - **PR #11312 (open) adds a `WiderThanDeclared` entry to the same parity file.** The two diffs touch disjoint blocks. Whichever lands second merges `main` and re-derives the totals by parser count. - The optional `with-item-count` catalog id rename was not done, because this round does not touch that file. - Commit trailers are the model-free pair AGENTS.md requires. - NOT MEASURED: - the full `pnpm test` farm and the repo-wide lint (CI's); - `apps/console` tests, because no file it owns changed; - an at-tier contract record, which the seat owes on the landing head. --- _Generated by [Claude Code](https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent be52115 commit b5b928a

15 files changed

Lines changed: 569 additions & 167 deletions

‎.changeset/6152-unmirrored-round4.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ minor bump, per the repository's version policy.
4848

4949
`displayMode` on the two chatbot types is unchanged: its refusal stays TypeScript-only, so stored
5050
designer documents that carry `displayMode: 'floating'` parse exactly as before.
51+
52+
**Correction, 2026-10-01 (objectui#6152, round 5).** The "Mirrored" section above says that on a `chatbot` node `floatingConfig` stays unvalidated. That was true when this change was written, and it no longer is: objectui#6152 round 5 retired `floatingConfig` on the `chatbot` type, so writing it on a `chatbot` node is now a `tsc` error and a parse error that names `chatbot-floating`. The `chatbot-floating` node keeps its `floatingConfig`, judged member by member, as this entry says.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
'@object-ui/types': minor
3+
'@object-ui/components': minor
4+
---
5+
6+
feat(types)!: retire `data-table`'s `selectionStyle` and `chatbot`'s `floatingConfig` on both faces, and stop teaching `data-table`'s inline-edit flags as authored keys (objectui#6152, round 5)
7+
8+
**Retired (breaking).** Each key below was declared on a published TypeScript type in
9+
`@object-ui/types` and unknown to its zod mirror in `@object-ui/types/zod`, so the tolerant
10+
validator kept an authored value without examining it. Each is retired at once, with no alias
11+
window:
12+
13+
- `data-table`: `selectionStyle` (`'always' | 'hover'`). The `data-table` renderer in
14+
`@object-ui/components` honoured `'hover'` by hiding each row's selection checkbox until the
15+
row was hovered, but nothing in this repository or in `objectstack` authored or produced the
16+
key. The hover-only branch is removed: a selectable table always shows its row checkboxes,
17+
which is what `'always'` and an unset key already did. Delete the key; `selectable` alone
18+
turns selection on.
19+
- `chatbot`: `floatingConfig`. The `chatbot` renderer never read it, so a value on a `chatbot`
20+
node configured nothing. The trigger and panel it describes belong to the floating
21+
presentation: author `type: 'chatbot-floating'` with `floatingConfig`. That node's
22+
`floatingConfig` is unchanged on both faces, and it is still validated member by member.
23+
24+
For each retired key:
25+
26+
- the TypeScript member is now `?: never`, so writing it is a `tsc` error;
27+
- the zod mirror refuses it by name at the key, on the tolerant validator (`AnyComponentSchema`,
28+
`safeValidateSchema`) and on the strict authoring face (`StrictAnyComponentSchema`) alike. The
29+
strict face used to refuse it as an unknown key with no guidance; the refusal now says what to
30+
write instead.
31+
32+
Delete the key from any document or literal that carries it.
33+
34+
**Docs: `data-table`'s inline-edit flags are host-paired.** `editable` and `singleClickEdit`
35+
stay declared on `DataTableSchema`, unchanged, but the `data-table` page no longer teaches them
36+
as keys a document sets. The table only stages an edit, and saving it needs callbacks a host
37+
supplies in code. `object-grid` sets both keys on the table it builds and supplies that save
38+
path; a document that wants inline editing authors an `object-grid`. Their doc comments say
39+
the same.
40+
41+
`@object-ui/types` and `@object-ui/components` are in the fixed release group, so this ships as a
42+
minor bump, per the repository's version policy.

‎.changeset/7654-floating-chatbot-trigger-icon-tombstone.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,5 @@ mirror must add the `retirementTombstone()` half at the same time and flip the c
7070
rather than delete it into a vacuum.
7171

7272
**Correction, 2026-09-30 (objectui#6152, round 4).** The sections above say `FloatingChatbotConfig` has no zod mirror, so the `triggerIcon` refusal is type-level only and the runtime face does not change. That was true when this change was written, and it is now true on a `chatbot` node only. objectui#6152 round 4 minted the mirror on the `chatbot-floating` node, the one whose renderer reads `floatingConfig`, together with the `retirementTombstone()` half this entry asked for. So a `chatbot-floating` node that carries `floatingConfig.triggerIcon` is now refused at that path. The tripwire test was flipped for that node, not deleted.
73+
74+
**Correction, 2026-10-01 (objectui#6152, round 5).** The correction above says the `triggerIcon` refusal is still type-level only on a `chatbot` node. That no longer holds: objectui#6152 round 5 retired `floatingConfig` itself on the `chatbot` type, on both faces, so a `chatbot` node refuses the whole key, `triggerIcon` with it, at compile time and at parse time. The only node that still takes `floatingConfig` is `chatbot-floating`, where `triggerIcon` is refused on both faces as described above.

‎.changeset/7655-chatbot-registration-authoring-faces.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,5 @@ the zod twins. The "twenty keys" above is kept as the reading of this change; th
118118
objectui#5605 retirement entry states what the three faces declare now.
119119

120120
**Correction, 2026-09-30 (objectui#6152, round 4).** The "Zod twins" section above says the floating twin leaves `floatingConfig` unmirrored because no `FloatingChatbotConfig` mirror exists. That was true when this change was written, and it no longer is: objectui#6152 round 4 minted that mirror and declared `floatingConfig` on the `chatbot-floating` twin, judged member by member. The same round declared `requestBody` on `ChatbotSchema`'s twin, so all three chatbot twins now share one `requestBody` arm. `displayMode` stays unmirrored on both twins, as this entry says.
121+
122+
**Correction, 2026-10-01 (objectui#6152, round 5).** The section above says `ChatbotSchema` keeps `floatingConfig` as a typed member, so the `triggerIcon` tombstone reaches `chatbot` nodes. That is no longer true: objectui#6152 round 5 retired `ChatbotSchema.floatingConfig` on both faces, because the `chatbot` registration never read it. The member is a `?: never` tombstone on the TypeScript face and a named refusal on the zod twin, so a `chatbot` node refuses the whole key. `ChatbotFloatingSchema.floatingConfig` is unchanged; it never inherited the base member.

‎content/docs/components/complex/data-table.mdx‎

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,6 @@ interface DataTableSchema {
4545
resizableColumns?: boolean; // Allow column resizing (default: true)
4646
reorderableColumns?: boolean; // Allow column reordering (default: true)
4747
48-
// Inline editing
49-
editable?: boolean; // Enable inline cell editing (default: false)
50-
singleClickEdit?: boolean; // Enter edit mode on single click (default: false)
51-
renderCellEditor?: (ctx: { // Host-supplied editor widget; null -> built-in input
52-
column: any;
53-
row: any; // the persisted record
54-
pendingRow: any; // row + this row's staged, unsaved edits (#7188)
55-
value: any;
56-
stage: (v: any) => void;
57-
commit: (v?: any) => void;
58-
cancel: () => void;
59-
}) => ReactNode;
60-
6148
// Styling
6249
className?: string; // Tailwind CSS classes on the table wrapper
6350
cellClassName?: string; // Tailwind CSS classes on the utility cells only
@@ -111,15 +98,41 @@ Drop the per-column half and the data cells stay at the table primitive's defaul
11198

11299
## Inline editing
113100

114-
With `editable: true` a cell enters edit mode on double-click (or on single click
115-
with `singleClickEdit: true`) and the table renders one of its built-in editors —
116-
text, number, date — chosen from the column's `type`.
101+
Inline editing is a **host-paired** capability of this table, not a flag a document
102+
sets. The table only *stages* an edit; saving it is the job of callbacks that a host
103+
supplies in code (`onRowSave`, `onCellChange`, `onBatchSave`), and a JSON document
104+
cannot supply a function. So the keys that switch editing on — `editable`, and
105+
`singleClickEdit` for entering edit mode on the first click instead of a
106+
double-click — are set by the host on the `data-table` node it builds, together with
107+
that save path. `object-grid` is that host: it sets both keys on its table and
108+
passes its own save path, which writes through the data source.
109+
110+
**Do not author `editable` or `singleClickEdit` on a `data-table` node.** Without a
111+
host save path the edits are staged in the table and nothing persists them. To edit
112+
records inline from a document, author an `object-grid` instead — see
113+
[Inline Editing](/docs/plugins/plugin-grid#inline-editing) in the grid plugin.
114+
115+
When a host has switched editing on, a cell enters edit mode on double-click (or on
116+
a single click) and the table renders one of its built-in editors — text, number,
117+
date — chosen from the column's `type`.
118+
119+
The host can also supply `renderCellEditor`, a function the table calls first for
120+
every cell it is about to edit; it returns a node to use, or `null` to fall through
121+
to the built-in editor for that column. This is how `object-grid` gives a `select`
122+
or `lookup` cell the same dedicated control the form uses, without the component
123+
layer having to re-implement it. Its context is:
117124

118-
`renderCellEditor` lets the host supply a widget instead. The table calls it first
119-
for every cell it is about to edit; return a node to use it, or `null` to fall
120-
through to the built-in editor for that column. This is how `object-grid` gives a
121-
`select` or `lookup` cell the same dedicated control the form uses, without the
122-
component layer having to re-implement it.
125+
```plaintext
126+
renderCellEditor?: (ctx: {
127+
column: any;
128+
row: any; // the persisted record
129+
pendingRow: any; // row + this row's staged, unsaved edits (#7188)
130+
value: any;
131+
stage: (v: any) => void;
132+
commit: (v?: any) => void;
133+
cancel: () => void;
134+
}) => ReactNode;
135+
```
123136

124137
The returned node is wrapped by the table so it inherits the exit-edit
125138
affordances the built-in editors have: Enter commits from a single-line input,

‎content/docs/plugins/plugin-chatbot.mdx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,12 @@ The six keys below are declared in the `chatbot-floating` registration's own
230230
`inputs` (`packages/plugin-chatbot/src/renderer.tsx`). They configure the
231231
floating action button and the panel it opens; the `chatbot` and
232232
`chatbot-enhanced` registrations render neither and ignore them. `floatingConfig`
233-
is declared on `ChatbotSchema` and on `ChatbotFloatingSchema` alike
234-
(objectui#7655 declared the floating face with the same member; `ChatbotSchema`
235-
kept its own), so authoring it on an inline node type-checks and parses - and
236-
is dropped at render time, because the `chatbot` node never read it.
233+
is declared on `ChatbotFloatingSchema` only. It used to be declared on
234+
`ChatbotSchema` as well, so authoring it on a `chatbot` node type-checked and
235+
parsed, and was then dropped at render time, because the `chatbot` node never
236+
read it. objectui#6152 retired it there: writing `floatingConfig` on a `chatbot`
237+
node is now a compile error **and** a parse error that names `chatbot-floating`,
238+
the node type that reads it. `ChatbotEnhancedSchema` never declared it.
237239

238240
**There is no `displayMode` key.** The presentation is selected by the node's
239241
own `type`: author a `chatbot-floating` node for the trigger-and-panel

‎packages/components/src/renderers/complex/data-table.tsx‎

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,6 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
777777
reorderableColumns = true,
778778
editable = false,
779779
singleClickEdit = false,
780-
selectionStyle = 'always',
781780
rowClassName,
782781
rowStyle,
783782
className,
@@ -2426,20 +2425,13 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
24262425
}}
24272426
>
24282427
{selectable && (
2429-
<TableCell className={cn(cellClassName, "px-3", frozenColumns > 0 && "sticky left-0 z-10 bg-background", selectionStyle === 'hover' && "relative")}>
2430-
{selectionStyle === 'hover' ? (
2431-
<div className={cn("transition-opacity", isSelected ? "opacity-100" : "opacity-0 group-hover/row:opacity-100")}>
2432-
<Checkbox
2433-
checked={isSelected}
2434-
onCheckedChange={(checked) => handleSelectRow(rowId, checked as boolean)}
2435-
/>
2436-
</div>
2437-
) : (
2438-
<Checkbox
2439-
checked={isSelected}
2440-
onCheckedChange={(checked) => handleSelectRow(rowId, checked as boolean)}
2441-
/>
2442-
)}
2428+
<TableCell className={cn(cellClassName, "px-3", frozenColumns > 0 && "sticky left-0 z-10 bg-background")}>
2429+
{/* Always visible: the hover-only `selectionStyle` was retired
2430+
(objectui#6152 round 5) — nothing authored or produced it. */}
2431+
<Checkbox
2432+
checked={isSelected}
2433+
onCheckedChange={(checked) => handleSelectRow(rowId, checked as boolean)}
2434+
/>
24432435
</TableCell>
24442436
)}
24452437
{showRowNumbers && (

‎packages/types/src/__tests__/chatbot-registration-authoring-faces-7655.test.ts‎

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@
4040
*
4141
* ## `displayMode` and `floatingConfig` live on BOTH faces; `displayMode` is a tombstone on both
4242
*
43-
* `ChatbotSchema` keeps `floatingConfig` exactly as it had it, and
44-
* `ChatbotFloatingSchema` declares the same member. `displayMode` was carried
43+
* `ChatbotSchema` kept `floatingConfig` exactly as it had it, and
44+
* `ChatbotFloatingSchema` declares the same member. (objectui#6152 round 5 has
45+
* since RETIRED `ChatbotSchema.floatingConfig` on both faces — the `chatbot`
46+
* registration never read it — so the member is live on the floating face only;
47+
* the pins below read that.) `displayMode` was carried
4548
* the same way — declared verbatim on both faces, untouched by this card —
4649
* until objectui#7654 RETIRED it (maintainer ruling B, 2026-09-05): it is a
4750
* `?: never` tombstone on both faces now, the designer control and the
@@ -163,9 +166,10 @@ export type assertionFloatingDeclaresWhatItReads = Expect<
163166
/**
164167
* `chatbot` keeps its WHOLE face — the six legacy keys (`loading` … `height`),
165168
* the `onSendMessage` and (since objectui#7654) `displayMode` tombstones, and
166-
* `floatingConfig` — exactly where they were. A `?: never` member is still a
167-
* declared key, so the census does not move when a key is tombstoned. This
168-
* card declared faces; it retired and moved nothing.
169+
* `floatingConfig` (a tombstone too since objectui#6152 round 5) — exactly where
170+
* they were. A `?: never` member is still a declared key, so the census does not
171+
* move when a key is tombstoned. This card declared faces; it retired and moved
172+
* nothing.
169173
*/
170174
export type assertionChatbotKeepsItsWholeFace = Expect<
171175
Equal<
@@ -177,19 +181,20 @@ export type assertionChatbotKeepsItsWholeFace = Expect<
177181
>;
178182

179183
/**
180-
* The two floating keys stay TYPED on `ChatbotSchema`. Read off the member,
181-
* not the key set: a member that fell off the declaration would not go missing
182-
* here — it would read as `any` through `BaseSchema`'s index signature, wrong
183-
* values would compile, and the objectui#7669 `triggerIcon` tombstone would lose
184-
* its reach on `chatbot` nodes (all three measured on #7655's first cut, which
185-
* moved the keys). `Equal` is what catches the `any`. `displayMode` reads as
186-
* `undefined` since objectui#7654 tombstoned it (`?: never` without
187-
* `exactOptionalPropertyTypes` is `never | undefined`, which collapses) — a
188-
* reading `Equal` still tells apart from the `any` a deletion would leave.
184+
* The two floating keys stay DECLARED on `ChatbotSchema` — as tombstones now. Read
185+
* off the member, not the key set: a member that fell off the declaration would not
186+
* go missing here — it would read as `any` through `BaseSchema`'s index signature,
187+
* any value would compile on a `chatbot` node (measured on #7655's first cut, which
188+
* moved the keys off this face by DELETION). `Equal` is what catches the `any`.
189+
* `displayMode` reads as `undefined` since objectui#7654 tombstoned it, and
190+
* `floatingConfig` since objectui#6152 round 5 RETIRED it on this face (the `chatbot`
191+
* registration never read it): `?: never` without `exactOptionalPropertyTypes` is
192+
* `never | undefined`, which collapses — a reading `Equal` still tells apart from
193+
* the `any` a deletion would leave.
189194
*/
190195
export type assertionFloatingKeysStayTypedOnChatbot = [
191196
Expect<Equal<ChatbotSchema['displayMode'], undefined>>,
192-
Expect<Equal<ChatbotSchema['floatingConfig'], FloatingChatbotConfig | undefined>>,
197+
Expect<Equal<ChatbotSchema['floatingConfig'], undefined>>,
193198
];
194199

195200
/* ── One declaration per shared key: a pick, not a copy ──────────────────── */
@@ -216,11 +221,15 @@ export type assertionMaxToolRoundtripsIsATombstoneOnEveryFace = [
216221
Expect<Equal<ChatbotFloatingSchema['maxToolRoundtrips'], undefined>>,
217222
];
218223

219-
/* ── `displayMode` / `floatingConfig`: one type, both faces ─────────────── */
224+
/* ── `displayMode`: one type on both faces; `floatingConfig`: live on the floating face only ── */
220225

226+
/**
227+
* `floatingConfig` had one type on both faces until objectui#6152 round 5 retired the
228+
* `chatbot` face's copy; the floating face never inherited it (its `Pick` leaves it
229+
* out) and keeps its own declaration. The second row is the half that must NOT move.
230+
*/
221231
export type assertionFloatingKeysHaveOneTypeOnBothFaces = [
222232
Expect<Equal<ChatbotFloatingSchema['displayMode'], ChatbotSchema['displayMode']>>,
223-
Expect<Equal<ChatbotFloatingSchema['floatingConfig'], ChatbotSchema['floatingConfig']>>,
224233
// Both faces carry the objectui#7654 tombstone, so both read `undefined`.
225234
Expect<Equal<ChatbotFloatingSchema['displayMode'], undefined>>,
226235
Expect<Equal<ChatbotFloatingSchema['floatingConfig'], FloatingChatbotConfig | undefined>>,
@@ -448,13 +457,19 @@ describe('`ChatbotFloatingSchema` (zod) validates what the face declares, and le
448457
});
449458

450459
it('`floatingConfig` is mirrored here since objectui#6152 round 4, and a wrong member value is refused at its path', () => {
451-
// This registration is the one that reads the key; `ChatbotSchema`'s twin,
452-
// whose registration never does, still has no arm for it.
460+
// This registration is the one that reads the key. `ChatbotSchema`'s twin,
461+
// whose registration never does, had no arm for it until objectui#6152 round 5
462+
// RETIRED it there — a tombstone that refuses ANY value, the valid one this
463+
// twin accepts included. Pinned on both twins so neither half can move alone.
453464
expect((ChatbotFloatingZod.shape as Record<string, unknown>).floatingConfig).toBeDefined();
454465
const wrong = ChatbotFloatingZod.safeParse({ ...node, floatingConfig: { panelHeight: '520px' } });
455466
expect(wrong.success).toBe(false);
456467
expect(wrong.error?.issues.some((i) => i.path.join('.') === 'floatingConfig.panelHeight')).toBe(true);
457-
expect((ChatbotZod.shape as Record<string, unknown>).floatingConfig).toBeUndefined();
468+
const valid = { panelHeight: 520, title: 'Support' };
469+
expect(ChatbotFloatingZod.safeParse({ ...node, floatingConfig: valid }).success).toBe(true);
470+
const onChatbot = ChatbotZod.safeParse({ ...node, type: 'chatbot', floatingConfig: valid });
471+
expect(onChatbot.success).toBe(false);
472+
expect(onChatbot.error?.issues.map((i) => [i.path.join('.'), i.code])).toEqual([['floatingConfig', 'invalid_type']]);
458473
});
459474

460475
it('`onClear` / `onError` / `onSend` are refused by name here too', () => {

0 commit comments

Comments
 (0)