From 4e2b7a615b8c3cd65ff1c73f75e1a379974ef376 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 19 Sep 2026 02:16:53 +0000 Subject: [PATCH 1/2] fix(components): spell the shipped `tabs` default items `content`, the key `TabItemSchema` declares All three items in the `ui:tabs` registration's `defaultProps` spelled `body` and omitted `content`, so the default an author drops on a canvas was refused by the validator this repository publishes for it: invalid_type ["content"] expected nonoptional, received undefined `TabItemSchema` declares `content` with no `.optional()` and declares no `body` member at all, so the key the seed carried was stripped by the parse and the key the parse required was absent. No rendered output moves. `tabs.tsx` reads `item.content` first and falls back to `body` through an `any` cast, so the same nodes move from the fallback arm onto the primary one; the repair is at the parse. Both halves are asserted, not assumed, by the new pin. The pin parses whatever the registry actually carries through the published schema rather than checking the spelling, so a re-spelling, a fourth item that omits `content`, or a moved seed all fail it. Its control A feeds the pre-fix item to the same call and requires a refusal naming `content`, so a parse that cannot refuse anything cannot pass as green. The `|| (item as any).body` fallback and `TabItemSchema` itself are untouched: widening the published accept set to admit `body` would grow an accept set that is already shipped and pre-empt the dialect question that is still open. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq --- .changeset/9941-tabs-default-items-content.md | 23 ++ .../tabs-default-items-parse-9941.test.tsx | 230 ++++++++++++++++++ .../components/src/renderers/layout/tabs.tsx | 6 +- 3 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 .changeset/9941-tabs-default-items-content.md create mode 100644 packages/components/src/renderers/layout/__tests__/tabs-default-items-parse-9941.test.tsx diff --git a/.changeset/9941-tabs-default-items-content.md b/.changeset/9941-tabs-default-items-content.md new file mode 100644 index 0000000000..83a8346a0b --- /dev/null +++ b/.changeset/9941-tabs-default-items-content.md @@ -0,0 +1,23 @@ +--- +'@object-ui/components': patch +--- + +The shipped `ui:tabs` registration's `defaultProps.items` now spell `content`, the +child key the published `TabItemSchema` declares, instead of the undeclared `body` +(objectui#9941). + +All three seeded items spelled `body` and omitted `content`, so the default an +author drops on a canvas was refused by the validator this repository publishes +for it — `invalid_type ["content"] expected nonoptional, received undefined`, +measured against the built schema, with the lit control that the same item spelled +`content` parses green with keys `value,label,content`. + +⚠️ No rendered output moves. `tabs.tsx` reads `item.content` first and falls back +to `body` through an `any` cast, so the same nodes move from the fallback arm onto +the primary one; the repair is at the parse and the rendered markup is unchanged +(asserted, not assumed). What changes for an author is that copying the shipped +default into authored metadata now validates. + +The fallback arm itself is untouched and `TabItemSchema` is untouched — widening +the published accept set to admit `body` would pre-empt the `body`-dialect +question open on objectui#9871. diff --git a/packages/components/src/renderers/layout/__tests__/tabs-default-items-parse-9941.test.tsx b/packages/components/src/renderers/layout/__tests__/tabs-default-items-parse-9941.test.tsx new file mode 100644 index 0000000000..36dbff3140 --- /dev/null +++ b/packages/components/src/renderers/layout/__tests__/tabs-default-items-parse-9941.test.tsx @@ -0,0 +1,230 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * The shipped `ui:tabs` registration's `defaultProps.items` satisfy the + * published `TabItemSchema` (objectui#9941). + * + * ## The defect this pin was built around + * + * `TabItemSchema` (`packages/types/src/zod/layout.zod.ts`, cited by SYMBOL — + * objectui#8875) declares `content` with **no** `.optional()` — required — and + * declares **no `body` member at all**. The registration seeded three items + * that spelled `body` and omitted `content`, so the default an author drops on + * a canvas was refused by the validator this repository publishes for it: + * + * invalid_type ["content"] expected nonoptional, received undefined + * + * ⚠️ The defect was never visible in the rendered UI. `tabs.tsx` reads + * `renderChildren(item.content || (item as any).body)` — `content` first, `body` + * through an `any` cast — so the seeded nodes drew from the fallback arm. It was + * the DECLARED contract that refused them, and only a parse can see that. + * + * ## Why this pin parses instead of reading the spelling + * + * A test asserting the three items spell `content` is satisfied forever by a + * copy-paste of the literal; it re-states the fix instead of checking it. This + * one feeds whatever the registry actually carries through the published schema, + * so it fails the day anyone re-spells a member, adds a fourth item that omits + * `content`, or moves the seed — and it fails for the same reason the card was + * filed, not for a reason a reader has to reconstruct. + * + * Both faces are read off live artifacts: the seed off `ComponentRegistry` (the + * object a designer palette consumes), the contract off `@object-ui/types/zod` + * (the object a validator consumes). Nothing below is a copy of either. + * + * ## Which rows discriminate, and which are controls + * + * ⚠️ The `controlB` rows are green in BOTH worlds BY DESIGN — that is the whole + * claim of this card. The repair is at the parse, so a row that moved with it + * would be evidence the repair was visual after all. The rows that discriminate + * are the three subject rows; `controlA` and `controlB` exist to prove the + * instrument can fail and that the UI did not follow, respectively. + * + * ## The instrument's two failure modes, each with its own control + * + * 1. **A parse over nothing is green.** An empty (or missing) `items` array + * makes `every item parses` vacuously true, so the seed's presence and + * non-emptiness are asserted first, as their own rows. + * 2. **A parse that cannot refuse anything is green.** `controlA` feeds the + * pre-fix item — the same objects with `content` re-spelled back to `body` — + * to the same call and requires a refusal that names `content`. If that row + * goes green the subject rows below measure nothing. + * + * ## ⛔ What this pin deliberately does NOT claim + * + * - ⛔ It does not assert that `body` is refused. `TabItemSchema` is a stripping + * `z.object`, so an undeclared `body` is silently DROPPED, not rejected; + * `undeclaredBodyIsDropped` records that as the measured behaviour rather + * than letting a later reader assume a strict face. + * - ⛔ It does not touch the `|| (item as any).body` fallback in `tabs.tsx`. + * Retiring that tolerance is the `body`-dialect family's question + * (objectui#9871, objectui#9910 — both open, both `needs-user-decision`), and + * a pin here must not answer it. ⚠️ `controlB.equality` below is the one row + * that READS that arm: it exists to prove this repair was parse-level and not + * visual. When the fallback is retired, that row retires WITH it — ⛔ it is + * not a reason to weaken the subject, and the durable half of control B + * (`controlB.stillRenders`) does not depend on the arm at all. + * - ⛔ It is not widened past `ui:tabs`. Whether a registration's `defaultProps` + * may diverge from its published face across the repository is the open + * question on objectui#4631 (`pm:on-hold`), and deciding it by gate rather + * than by ruling is the trap the sibling pin in this directory + * (`registration-defaults-match-renderer-8229.test.ts`) documents at length. + */ + +import { describe, it, expect, afterEach } from 'vitest'; +import { render, cleanup } from '@testing-library/react'; +import { ComponentRegistry } from '@object-ui/core'; +// Registered at module scope, NOT in a hook: a cold transform billed to +// `hookTimeout` is narrower than the timeout it replaces (objectui#3010). +import '../../../renderers'; +import { TabItemSchema, TabsSchema } from '@object-ui/types/zod'; + +afterEach(() => cleanup()); + +type Item = Record; + +const meta = () => ComponentRegistry.getMeta('tabs', 'ui'); +const seed = () => (meta()?.defaultProps ?? {}) as Record; +const seededItems = () => (seed().items ?? []) as Item[]; + +/** + * The pre-fix spelling, rebuilt from whatever the registry carries today: the + * item's child list, wherever it is spelled, moved onto `body` with `content` + * gone. Reading BOTH spellings keeps this a faithful inverse under ablation — + * re-spell the seed back to `body` and this returns the seed unchanged, so the + * control rows stay controls and only the subject rows move. + */ +const preFix = (items: Item[]): Item[] => + items.map(({ content, body, ...rest }) => ({ ...rest, body: content ?? body })); + +/** Render the registration seed as a `tabs` node and return what it paints. */ +function renderSeed(items: Item[]): { text: string; html: string } { + const C = ComponentRegistry.get('tabs', 'ui'); + if (!C) throw new Error('no renderer registered for ui:tabs'); + render(); + // Radix mints a fresh id per mount, so two renders of identical content + // differ in `id`/`aria-controls`/`aria-labelledby` and nowhere else. Blank + // those three out; everything that carries meaning survives. + const html = (document.body.innerHTML ?? '').replace( + /(id|aria-controls|aria-labelledby)="[^"]*"/g, + '$1="#"', + ); + return { text: document.body.textContent ?? '', html }; +} + +describe('`ui:tabs` defaultProps.items satisfy the published TabItemSchema (objectui#9941)', () => { + describe('the seed is really there — ⛔ a parse over nothing is vacuously green', () => { + it('the registration is registered and carries a defaultProps seed', () => { + expect(meta(), 'ui:tabs is not registered — every row below is vacuous').toBeDefined(); + expect(meta()?.defaultProps).toBeDefined(); + }); + + it('the seed carries a non-empty `items` array', () => { + expect(Array.isArray(seed().items)).toBe(true); + expect(seededItems().length).toBeGreaterThan(0); + }); + }); + + describe('controlA — the instrument can FAIL', () => { + it('the pre-fix item is refused, and the refusal names `content`', () => { + // The exact reading objectui#9941 was filed on, re-taken through the same + // call the subject rows use. Structured fields only — ⛔ no message prose. + const refused = preFix(seededItems()).map((item) => TabItemSchema.safeParse(item)); + expect(refused.length).toBeGreaterThan(0); + for (const r of refused) { + expect(r.success).toBe(false); + if (r.success) continue; + expect(r.error.issues.map((i) => ({ code: i.code, path: i.path.join('.') }))).toContainEqual({ + code: 'invalid_type', + path: 'content', + }); + const missing = r.error.issues.find((i) => i.path.join('.') === 'content'); + expect((missing as { expected?: string } | undefined)?.expected).toBe('nonoptional'); + } + }); + + it('undeclaredBodyIsDropped — `body` is stripped by the parse, ⛔ not rejected', () => { + // Why the refusal above is about the ABSENT key and never about the + // present one: the face is a stripping `z.object`, so the item's own + // child list did not survive the parse even before the missing-key error. + const parsed = TabItemSchema.parse({ + value: 'probe', + label: 'probe', + content: [{ type: 'text', content: 'kept' }], + body: [{ type: 'text', content: 'dropped' }], + }); + expect(Object.keys(parsed).sort()).toEqual(['content', 'label', 'value']); + expect(parsed).not.toHaveProperty('body'); + }); + }); + + describe('the subject', () => { + it('every seeded item parses green against `TabItemSchema`', () => { + const failures = seededItems() + .map((item, i) => ({ i, r: TabItemSchema.safeParse(item) })) + .filter(({ r }) => !r.success) + .map(({ i, r }) => + `items[${i}]: ${(r as { error: { issues: { code: string; path: PropertyKey[] }[] } }).error.issues + .map((issue) => `${issue.code} [${issue.path.join('.')}]`) + .join(', ')}`, + ); + expect( + failures, + 'the shipped default is the metadata an author drops on a canvas — it has to ' + + 'satisfy the schema this repository publishes for it. `TabItem`\'s child key is ' + + '`content` (⛔ not `body`, ⛔ not `children`); spell the seed that way rather than ' + + 'widening `TabItemSchema`, which would grow an already-published accept set and ' + + 'pre-empt objectui#9871.', + ).toEqual([]); + }); + + it('the whole registration seed parses as a `tabs` node', () => { + // One step above the item face: what the designer writes out is the node, + // not a loose item. Green here is the end-to-end statement of the repair. + const r = TabsSchema.safeParse({ type: 'tabs', ...seed() }); + expect(r.success, JSON.stringify(r.success ? [] : r.error.issues, null, 1)).toBe(true); + }); + + it('each seeded item keeps a child list after the parse', () => { + // Green-because-empty guard on the row above: an item whose `content` was + // an empty array would parse and carry nothing. + for (const item of seededItems()) { + const parsed = TabItemSchema.parse(item) as { content: unknown }; + expect(parsed.content).toBeDefined(); + expect(Array.isArray(parsed.content) ? (parsed.content as unknown[]).length : 1).toBeGreaterThan(0); + } + }); + }); + + describe('controlB — the UI did not move', () => { + it('stillRenders — the seed paints every label and the first tab body', () => { + // Durable half: reads only the `content` arm, so it survives whatever + // objectui#9871 rules about the fallback. + const { text } = renderSeed(seededItems()); + for (const item of seededItems()) expect(text).toContain(String(item.label)); + expect(text).toContain('Content for Tab 1'); + }); + + it('equality — the pre-fix spelling paints exactly the same thing today', () => { + // ⚠️ THIS ROW READS THE FALLBACK ARM (`item.content || (item as any).body`). + // It is the proof that objectui#9941 was a parse-level repair and not a + // visual one: the same nodes moved from the fallback arm onto the primary + // one. It is ⛔ NOT a claim that the fallback must stay — when + // objectui#9871 retires it this row goes with it, and `stillRenders` + // above, plus every subject row, are untouched by that. + const after = renderSeed(seededItems()); + cleanup(); + const before = renderSeed(preFix(seededItems())); + expect(before.text).toBe(after.text); + expect(before.html).toBe(after.html); + // Lit control: ⛔ two empty renders are also equal. + expect(after.text).toContain('Content for Tab 1'); + }); + }); +}); diff --git a/packages/components/src/renderers/layout/tabs.tsx b/packages/components/src/renderers/layout/tabs.tsx index 502350f2bc..cfa19819de 100644 --- a/packages/components/src/renderers/layout/tabs.tsx +++ b/packages/components/src/renderers/layout/tabs.tsx @@ -85,9 +85,9 @@ ComponentRegistry.register('tabs', TabsRenderer, defaultProps: { defaultValue: 'tab1', items: [ - { label: 'Tab 1', value: 'tab1', body: [{ type: 'text', content: 'Content for Tab 1' }] }, - { label: 'Tab 2', value: 'tab2', body: [{ type: 'text', content: 'Content for Tab 2' }] }, - { label: 'Tab 3', value: 'tab3', body: [{ type: 'text', content: 'Content for Tab 3' }] } + { label: 'Tab 1', value: 'tab1', content: [{ type: 'text', content: 'Content for Tab 1' }] }, + { label: 'Tab 2', value: 'tab2', content: [{ type: 'text', content: 'Content for Tab 2' }] }, + { label: 'Tab 3', value: 'tab3', content: [{ type: 'text', content: 'Content for Tab 3' }] } ], className: 'w-full' } From 2b807400ca37889358340d2bf6297c837d05e4a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 19 Sep 2026 04:16:54 +0000 Subject: [PATCH 2/2] test(scripts): re-point the `body`-dialect producer-table claims, scoped to the emission criterion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three `tabs.tsx` `defaultProps` items respelled in the previous commit were the whole producer table under objectui#9871's criterion, so it is now empty and four live-tree claims across the two scan test files went red. Each of them carried an instruction from its own author for exactly this event — re-point the block and say so in those terms, do not delete the claim — and that is what this does. Measured both sides with the unchanged scan against the same uninstalled tree: base producers=3 filesScanned=5152 readers=4 hits=1069 failedC2=313 unclassified=5 all three carrier 'item', channel 'default-props', disposition 'unruled:item-carrier', at tabs.tsx :88 :89 :90 head producers=0 filesScanned=5153 readers=4 hits=1068 failedC2=313 unclassified=5 Each re-pointed claim says, and does not exceed: 1. the table is empty UNDER objectui#9871's EMISSION criterion; 2. its last subject was the three tabs.tsx items, respelled to `content` because `TabItemSchema` declares `content` required and declares no `body` — a conformance fix, not a dialect migration; 3. this is NOT the repo-wide finish line and no block here claims it is. `failedC2` held at 313 and `unclassified` at 5 across the same change, so 318 rows outside what the criterion scores as emission did not move, and a published doc is still teaching the spelling. Emitted and taught are different verbs; this scan reads only the first; 4. the instrument is not blind. The DIFFERENTIAL test plants `ITEM_PRODUCER` in a synthetic root and still asserts carrier ['item'], line 6, channel 'default-props', and it passed in the same run that reported the reds. Every re-pointed block names that fixture, because it is what makes standing down a live-tree control safe rather than blind. Two assertions that now pass over an empty set are marked vacuous in place rather than left silent: the string-carried `toBe(0)` and the disposition loop over item-carried producers. Both are kept, because each is the claim that fires the moment a producer returns. Nothing was deleted, no assertion was skipped, and the scan itself is untouched: its behaviour is right, its subject moved. Totals are unchanged at 46 tests across the two files (was 2 failed | 44 passed, now 46 passed), and packages/components/ holds at 283 files / 2752 tests. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq --- scripts/__tests__/body-dialect-census.test.ts | 50 +++++++++--- .../body-dialect-producer-scan.test.ts | 80 ++++++++++++++++--- 2 files changed, 110 insertions(+), 20 deletions(-) diff --git a/scripts/__tests__/body-dialect-census.test.ts b/scripts/__tests__/body-dialect-census.test.ts index 86d3c9dd7f..894fd5c7a7 100644 --- a/scripts/__tests__/body-dialect-census.test.ts +++ b/scripts/__tests__/body-dialect-census.test.ts @@ -374,8 +374,23 @@ describe('the `body` consumers the ruling does not enumerate', () => { // rather than reasoned, both legs, against the built parser: a `card` node // carrying `body` draws `unknown-prop: has no prop "body" — the // child-list key is "children"`, and a `tabs` item carrying the same key - // draws ZERO diagnostics. ⇒ an empty table is objectui#9590's finish line, - // ⛔ not step 4's gate. ⛔ Do not delete the claim to make the block green. + // draws ZERO diagnostics. + // + // ⭐ RE-POINTED at objectui#9941, ⛔ not deleted — the assertion below + // carried its own instruction to do exactly that, in these terms. The table + // emptied when the three `tabs.tsx` `defaultProps` items were respelled to + // `content`, the key `TabItemSchema` declares required while declaring no + // `body`: ⛔ a CONFORMANCE fix, ⛔ not a dialect migration. + // + // ⭐ And the re-pointed claim is SCOPED: the table is empty **under + // objectui#9871's EMISSION criterion**. ⛔ It is NOT a claim that the + // dialect has left the tree, and ⛔ NOT objectui#9590's finish line. + // Measured across the same change: `failedC2` held at 313 and + // `unclassified` at 5, so 318 rows outside what that criterion scores as + // emission did not move — and objectui#9989 is a published doc still + // TEACHING the spelling. ⭐ Emitted and taught are different verbs, and this + // scan reads only the first. Whether the finish line is reached is + // objectui#9590's to judge on its own record. const producerScan = scan(REPO_ROOT); // Two lit controls, because an empty table is the assertion's own shape: a @@ -391,20 +406,30 @@ describe('the `body` consumers the ruling does not enumerate', () => { const producers = producersOf(producerScan.hits, producerScan.readers); expect( producers.length, - 'the producer table is EMPTY — under objectui#9871\'s criterion nothing in shipped source ' + - 'emits the dialect any more. ⚠️ That is objectui#9590\'s finish line, ⛔ NOT step 4\'s gate: ' + - 'step 4 landed in objectui#6771 with this table non-empty, because everything left in it ' + - 'is item-carried and outside the ruled family. ' + - 'Re-point this block and say so in those terms; ⛔ do not delete the claim.' - ).toBeGreaterThan(0); + 'a producer is BACK — shipped source emits the dialect again under objectui#9871\'s ' + + 'EMISSION criterion, and it has not since objectui#9941. ⛔ Do not relax this to a ' + + 'range: name the site and its carrier, and re-read the criterion before deciding ' + + 'what a non-zero means here.' + ).toBe(0); // ⭐ The two SHAPES the census structurally cannot reach, asserted as // shapes rather than as paths: a producer arriving in a file nobody has - // named joins `producers` with no list to extend. + // named joins `producers` with no list to extend — that property is what + // this block holds, and it is unaffected by the occupancy being zero. + // + // ⚠️ B1's LIVE occupancy went to zero at objectui#9941; the three `tabs.tsx` + // `defaultProps` items were its last subjects. ⭐ The SHAPE is ⛔ not + // unreachable and this block is ⛔ not blind: + // `scripts/__tests__/body-dialect-producer-scan.test.ts`'s DIFFERENTIAL test + // plants `ITEM_PRODUCER` in a synthetic root and still asserts carrier + // ['item'], line 6, channel 'default-props' — it passed in the same run that + // first reported this red. That fixture is what makes standing down the + // live-tree control here safe rather than blind. expect( producers.filter((hit: { carrier: string }) => hit.carrier === 'item').length, - 'no item-carried producer — the B1 shape (a `body` on an object with no `type`)' - ).toBeGreaterThan(0); + 'an item-carried producer is BACK — the B1 shape (a `body` on an object with no `type`) ' + + 'is live in shipped source again. ⛔ Not a licence to relax this; name the site.' + ).toBe(0); // ⚠️ B2 IS INVERTED BY THIS CARD, and the inversion is what the merge with // `main` actually found rather than a tidy-up. objectui#9871 asserted a // string-carried producer EXISTED, and its only subjects were the three VS @@ -484,6 +509,9 @@ describe('the `body` consumers the ruling does not enumerate', () => { // `list`'s `items[].body`; `tabs` items and the `dashboard` widget key are // recorded on it by comment 5733850974, so this pointer resolves to a record // that actually carries the two shapes named here. + // ⚠️ VACUOUS SINCE objectui#9941 — the item-carried set is EMPTY, so this + // loop body never runs and asserts nothing. Kept, ⛔ not deleted: it is the + // disposition claim that fires the moment an item-carried producer returns. for (const hit of producers.filter((h: { carrier: string }) => h.carrier === 'item')) { expect(hit.disposition).toBe('unruled:item-carrier'); } diff --git a/scripts/__tests__/body-dialect-producer-scan.test.ts b/scripts/__tests__/body-dialect-producer-scan.test.ts index 00ed54e894..bb4487aaf4 100644 --- a/scripts/__tests__/body-dialect-producer-scan.test.ts +++ b/scripts/__tests__/body-dialect-producer-scan.test.ts @@ -553,8 +553,47 @@ describe('the LIVE tree, read through the criterion', () => { // ⭐ Asserted as SHAPES, not as a count and not as a list of paths: a // producer arriving in a file nobody named appears in `producers` without // anyone extending anything, which is the whole point of this card. - expect(producers.filter((p: { carrier: string }) => p.carrier === 'item').length).toBeGreaterThan(0); - expect(files.size).toBeGreaterThan(0); + // + // ⭐ RE-POINTED at objectui#9941, ⛔ not deleted — this block's own former + // message asked for exactly that when the table emptied. What it now says, + // and it says ⛔ nothing beyond it: + // + // 1. the table is EMPTY **under objectui#9871's EMISSION criterion** + // (C1 + C2). ⛔ That is not "the dialect has left the tree". + // 2. its last subject was the three `defaultProps` items in + // `packages/components/src/renderers/layout/tabs.tsx`, respelled + // `body:` -> `content:` under objectui#9941 because `TabItemSchema` + // declares `content` required and declares no `body` — ⛔ a + // CONFORMANCE fix, ⛔ not a dialect migration. The two are not one act. + // 3. ⛔ THIS IS NOT objectui#9590'S FINISH LINE AND THIS BLOCK DOES NOT + // CLAIM IT IS. Measured across that same change: `failedC2` 313 -> 313 + // and `unclassified` 5 -> 5, both identical — 318 rows sit outside + // what this criterion scores as emission and not one of them moved — + // and objectui#9989 is a published doc still TEACHING the spelling. + // ⭐ Emitted and taught are different verbs and this scan reads only + // the first. Whether the finish line is reached is objectui#9590's to + // judge on its own record. + // 4. ⭐ the instrument is ⛔ NOT blind, and that is what makes standing + // down a live-tree control safe rather than blind: the DIFFERENTIAL + // test at the top of this file plants `ITEM_PRODUCER` in a synthetic + // root and still asserts carrier ['item'], line 6, channel + // 'default-props'. It passed in the same run that first reported these + // reds. The SHAPE stays reachable; only its live occupancy is zero. + // + // ⛔ Do not relax either of these to a range — the file's own idiom for a + // shape that has emptied is `toBe(0)` plus a message naming what a non-zero + // means (see the string-carried assertion below). + expect( + producers.filter((p: { carrier: string }) => p.carrier === 'item').length, + 'an item-carried producer is BACK — the B1 shape (a `body` on an object with no `type`) ' + + 'is live in shipped source again, and it has not been since objectui#9941. ' + + '⛔ Not a licence to relax this: name the site and its carrier.', + ).toBe(0); + expect( + files.size, + 'a producer FILE is back, so the empty-table reading above no longer holds. ' + + '⛔ Re-read that reading before changing anything here.', + ).toBe(0); // ⚠️ B2 INVERTED BY objectui#6771, and this is the SECOND copy of the claim // — the census block carries the first. ⭐ Neither file was in the merge's @@ -580,6 +619,14 @@ describe('the LIVE tree, read through the criterion', () => { 'lit control — no literal-carried hit passes C2, so the zero below would measure C2 ' + 'rather than the emission channel it claims to measure', ).toBeGreaterThan(0); + // ⚠️ VACUOUS SINCE objectui#9941, and said out loud rather than left to be + // discovered by the next reader. `producers` is EMPTY, so every filter over + // it is 0 and this passes without searching for anything — the phantom-check + // shape. It is kept, ⛔ not deleted, because it is the claim that fires the + // moment any producer returns; until one does it measures nothing, and the + // two lit controls directly above are what keep B2's own sight under test in + // the meantime. ⇒ they are ⛔ not decoration now, they are the only live + // half of this paragraph. expect( producers.filter((p: { source: string }) => p.source !== 'code-key').length, 'a string-carried producer is BACK — objectui#6771 step 5 migrated the last of them. ' + @@ -601,15 +648,30 @@ describe('the LIVE tree, read through the criterion', () => { // `unknown-prop: has no prop "body" — the child-list key is // "children"`, and a `tabs` ITEM carrying the same key draws ZERO // diagnostics. ⇒ step 4 refuses nothing the platform still ships. - // An empty table is objectui#9590's finish line, ⛔ not step 4's gate. - // ⛔ When this reds because the table emptied, that is still the HANDOFF — - // say so here in those terms rather than deleting the assertion. + // + // ⭐ RE-POINTED at objectui#9941, ⛔ not deleted — this assertion's own + // former message asked for that, in these terms. The table emptied when the + // three `tabs.tsx` `defaultProps` items were respelled to `content` to + // satisfy `TabItemSchema`, which declares `content` required and declares no + // `body`: ⛔ a conformance fix, ⛔ not a dialect migration. + // + // ⛔ An empty table is ⛔ NOT asserted here to be objectui#9590's finish + // line. The claim is scoped to what was measured — empty UNDER + // objectui#9871's EMISSION criterion. Across the same change `failedC2` held + // at 313 and `unclassified` at 5, so 318 rows outside what this criterion + // scores as emission did not move, and objectui#9989 is a published doc + // still TEACHING the spelling. Whether the finish line is reached is + // objectui#9590's to judge on its own record. + // + // ⭐ Safe rather than blind: the DIFFERENTIAL test at the top of this file + // plants `ITEM_PRODUCER` in a synthetic root and still asserts carrier + // ['item'], line 6, channel 'default-props'. expect( producers.length, - 'the producer table is EMPTY — under this criterion nothing ships the dialect. That is ' + - 'objectui#9590\'s finish line, ⛔ NOT objectui#6771 step 4\'s gate: step 4 landed with ' + - 'this table non-empty and item-carried. Re-point this block, do not delete it.', - ).toBeGreaterThan(0); + 'a producer is BACK — under objectui#9871\'s EMISSION criterion shipped source emits the ' + + 'dialect again, and it has not since objectui#9941. ⛔ Do not relax this to a range: ' + + 'name the site and its carrier, and re-read the criterion before deciding what it means.', + ).toBe(0); // ⏱ Explicit — one tree-wide scan over 5,136 files, measured 4.0s here; the // CI shard is at least 1.9x slower (objectui#9871's timeout reading), and // the default 15s leaves too little room on a loaded shard. Same 60s and the