Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/button-container-exception-reason-6804.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
---

Makes the `button` entry in `scripts/container-declaration-baseline.json` a **permanent,
reasoned exception** instead of a provisional one, executing objectui#6804's maintainer
ruling (2026-08-30). objectui#6779 had excluded `button` from the ratchet-to-zero list
"pending a separate card"; that card has now been ruled, and the exclusion stands: `button`
does not declare `isContainer`.

The entry's `reason` now carries the ruling's ground rather than a forward reference —
`isContainer` means layout containment, not "this tag renders children", and `button` reads
`schema.children` only as a fallback for `schema.label`, so declaring it would make one
predicate mean two things and would delete the `Button` identifier from the JSX scope of
every `kind:'react'` page, against zero measured pull the other way. Its owning `issue`
moves from `objectui#6779` (which deferred the question) to `objectui#6804` (which answered
it), with the provenance kept in the reason.

This matters because an exception with no recorded ground is indistinguishable from a
missed one, and that indistinguishability is the mechanism behind this defect class's three
independent rediscoveries (objectui#3900 / objectui#6740 / objectui#6764).

Also records a measurement the note previously implied away: `button` is the only public
tag among the 45 violations listed, but not the only public tag in the containment story —
ADR-0080's `PUBLIC_BLOCKS` carries `badge` and `alert` as bare keys too, so of the 14 tags
the ruling covers, three are published contract. The 11 bare `sidebar-*` keys are not (the
public sidebar is the namespaced `page:sidebar`, which already declares `isContainer`).

Ledger and test prose only. No published behaviour changes, no registration's metadata is
altered, and every assertion that keeps the exception honest is unchanged.
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,10 +69,12 @@
* - 73 render no children and correctly keep the diagnostic;
* - 0 failed to render, so nothing was scored on an exception.
*
* The 45 are the stock. `button` is excluded by the ruling and pinned separately;
* the other 44 are `scripts/container-declaration-baseline.json`, which is a
* RATCHET TO ZERO and not an exemption list: red when an unlisted tag violates,
* and red again when a listed tag stops violating, so the file can only shrink.
* The 45 are the stock. `button` is excluded by ruling and pinned separately — that
* exclusion was made PERMANENT and reasoned by objectui#6804 (2026-08-30); see the
* `button` block below. The other 44 are `scripts/container-declaration-baseline.json`,
* which is a RATCHET TO ZERO and not an exemption list: red when an unlisted tag
* violates, and red again when a listed tag stops violating, so the file can only
* shrink.
*/

import { describe, it, expect } from 'vitest';
Expand DownExpand Up@@ -324,28 +326,46 @@ describe('the ratchet: no NEW undeclared container (objectui#6779)', () => {
);
});

describe('`button` is EXCLUDED by ruling, not fixed and not forgotten (objectui#6779)', () => {
describe('`button` is EXCLUDED by ruling — permanently, and with its ground (objectui#6804)', () => {
it(
'still renders children, still undeclared, still the only public one',
'still renders children, still undeclared, still the only public one of the 45',
async () => {
const rows = await census();
const baseline = readBaseline();
const button = rows.find((r) => r.type === 'button');

// The 2026-08-29 ruling excluded `button` from the ratchet list and
// ordered a separate card: it is the ONLY public-tier member of the 45, so
// declaring the flag would delete `Button` from the JSX scope of every
// `kind:'react'` page, and it reads `children` as a LABEL FALLBACK rather
// than as layout containment — a public-tier product decision, not a
// mechanical fix.
// objectui#6779's ruling (2026-08-29) carved `button` out of the ratchet
// list and ordered a separate card. objectui#6804 IS that card, and its
// 2026-08-30 ruling SETTLED the question: `button` does not declare
// `isContainer`, permanently. The ground — recorded in full in the
// baseline's `reason` field, which is where a reader who opens the ledger
// will look — is that `isContainer` means LAYOUT CONTAINMENT, while
// `button` reads `children` only as a fallback for `schema.label`, so
// declaring it would make one predicate mean two different things; that it
// is the only public-tier member of the 45, so the declaration would also
// delete `Button` from the JSX scope of every `kind:'react'` page; and
// that the pull the other way measured zero.
//
// ⇒ The `issue` field points at the card that RULED it (#6804), not at the
// one that deferred it (#6779, named in the reason as provenance): a
// reader following this pointer wants the decision, not the deferral.
//
// The ruling covers 14 tags — `button` plus the 13 `schema.body` readers —
// but only `button` is LISTED here, and that is not an omission: the other
// 13 do not violate today, and listing a non-violator trips the OTHER
// direction of the baseline's red. They are pinned as non-violators by the
// `schema.body` readers block below instead.
expect(Object.keys(baseline.excluded)).toEqual(['button']);
expect(baseline.excluded.button.issue).toBe('objectui#6779');
expect(baseline.excluded.button.issue).toBe('objectui#6804');

// Pinned as a live description rather than as prose, so the exclusion
// cannot outlive its reason: when that separate card lands and `button`
// is either declared or made non-rendering, this goes RED and the entry
// must be resolved instead of standing forever. That indefinite standing
// is the tail risk the triage seat recorded against option B.
// cannot outlive its reason. These three are exactly the facts the ruling
// rests on; if any one of them stops being true, this goes RED and the
// exception must be RE-RULED rather than quietly re-based on a premise
// that no longer holds. That is what keeps a REASONED exception
// distinguishable from an oversight — and their indistinguishability is
// the mechanism behind this class's three independent rediscoveries
// (objectui#3900 / objectui#6740 / objectui#6764).
expect(button?.rendersChildren, '`button` no longer renders children').toBe(true);
expect(button?.isContainer, '`button` now declares `isContainer` — resolve the exclusion').toBe(
false,
Expand DownExpand Up@@ -388,9 +408,17 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
});

it('the `schema.body` readers stay non-containers — `body` is a key the check never inspects', async () => {
// The population the ruling assigns to objectui#6771's separate B ruling,
// and the one a "children or body" predicate would collapse: these render
// `renderChildren(schema.body)` and never touch `schema.children`.
// The other 13 of objectui#6804's 14, and the population a "children or
// body" predicate would collapse: these render `renderChildren(schema.body)`
// and never touch `schema.children`. That 2026-08-30 ruling gives them the
// SAME answer as `button` — no `isContainer` — but they get no baseline
// entry while they do not violate, because listing a non-violator is the
// other direction of that file's red. If objectui#6771's retirement of the
// `body` dialect gives one of them a `children` read, that is when it earns
// a reasoned entry of its own. ⚠️ Two of them are PUBLIC — `badge` and
// `alert` — so that change would be moving published contract, not a
// mechanical fix. That is a measurement, so it is pinned rather than
// asserted in prose: see the block below.
// `validateTree`'s containment branch is guarded by `node.children?.length`
// ALONE, so no author writing `body` on them has ever drawn a false
// diagnostic. Measured: 13 bare keys, not the 10 objectui#6779 estimated —
Expand All@@ -404,6 +432,68 @@ describe('the predicate is RUNTIME, so the exception shapes need no skip-list (o
}, CENSUS_TIMEOUT);
});

describe('the public tier of the ruled 14 is THREE, not one (objectui#6804)', () => {
// The baseline's ⚠️ paragraph tells whoever implements objectui#6771 that two of
// the 13 tags they are about to give a `children` read are PUBLISHED CONTRACT.
// That is a measurement, and this file's convention — set by the 44's own
// paragraph, "measured; the pin asserts it" — is that a measured claim in the
// ledger names the pin holding it. Unpinned, the warning goes quietly false the
// day `badge` or `alert` leaves the public tier, in the one sentence written to
// stop an unmeasured public-tier change. That is this card's own defect shape one
// level up: a claim nothing can distinguish from a stale one.
//
// Read off the LIVE REGISTRY rather than off `PUBLIC_BLOCKS`, because the list is
// the INPUT and the registry is the FACT, and this is precisely a population
// where the two differ: `getPublicConfigs()` keys the contract by the curated tag
// it was listed under, so the namespaced `page:sidebar` enters the public set
// under THAT spelling while the bare `sidebar` registration never does. Grepping
// the list would score all 12 sidebar keys off one entry that belongs to none of
// them.
it('`badge` and `alert` are public; the 11 bare `sidebar-*` keys are not', async () => {
const rows = await census();
const byType = new Map(rows.map((r) => [r.type, r]));
const isPublic = publicTags();

// DIRECTION CONTROL, first and for the same reason the 44's block carries one:
// without it, "the sidebar keys are not public" is indistinguishable from "this
// reader returned nothing", and every absence below would pass vacuously.
// `button` is the known positive — it is the fact the exclusion above rests on.
expect(isPublic.size).toBeGreaterThan(0);
expect(isPublic.has('button'), 'the public reader resolved nothing — every absence below is vacuous').toBe(
true,
);

// (1) The two public body readers. `button` is the only public tag among the 45
// VIOLATIONS, but not the only public tag among the ruled 14 — that is the
// distinction the baseline note now draws and this holds it.
for (const type of ['badge', 'alert']) {
expect(
byType.get(type)?.isPublic,
`\`${type}\` left the public tier — the baseline's ⚠️ public-tier paragraph is now false, fix it`,
).toBe(true);
}

// (2) …and the eleven that are not, so "three" is a count and not a guess.
const sidebars = bareTags().filter((t) => t.startsWith('sidebar'));
expect(sidebars.length, 'the `sidebar-*` family changed size — re-measure the note').toBe(11);
expect(
sidebars.filter((t) => byType.get(t)?.isPublic),
'a bare `sidebar-*` key became public — declaring it would now delete a react-page identifier',
).toEqual([]);
expect(['button', 'badge', 'alert'].filter((t) => byType.get(t)?.isPublic).length).toBe(3);

// (3) And why that is not the whole sidebar story: the sidebar that IS public is
// the namespaced registration, and it already declares the flag — so it is not
// in this containment story at all, and a reader who finds `page:sidebar` in
// `PUBLIC_BLOCKS` must not conclude the bare family is public too.
expect(isPublic.has('page:sidebar')).toBe(true);
expect(
ComponentRegistry.getMeta('page:sidebar')?.isContainer,
'`page:sidebar` stopped declaring containment — it is now part of this story',
).toBe(true);
}, CENSUS_TIMEOUT);
});

describe('the baseline file is a ratchet, and says so (objectui#6779)', () => {
it('carries the shrink-only contract in its own note', () => {
// A reader who opens the file must not be able to mistake it for an
Expand Down
33 changes: 31 additions & 2 deletions scripts/container-declaration-baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,43 @@
"declaration provably removes nothing. `button` is the one that is -- which is why it",
"is excluded rather than listed.",
"",
"⚠️ AND THE PUBLIC TIER IS WIDER THAN THIS LIST'S ONE HIT -- do not read the paragraph",
"above as `button` being the only public tag in the containment story. It is the only",
"public tag among the 45 measured HERE. `PUBLIC_BLOCKS` (ADR-0080,",
"packages/core/src/registry/public-blocks.ts) also carries `badge` and `alert` as bare",
"keys, and both are registered bare. They are absent from this list only because they",
"render `renderChildren(schema.body)` and never touch `schema.children`, so they do not",
"violate -- yet. Measured 2026-08-30 over the live registry, and PINNED -- the ratchet",
"test's `the public tier of the ruled 14 is THREE, not one` block asserts every fact in",
"this paragraph, direction control included, so it cannot go quietly stale the way an",
"unheld measurement in a durable ledger does: of the 14 tags objectui#6804 ruled on,",
"THREE are public -- `button`, `badge`, `alert`. The 11 bare",
"`sidebar-*` keys are NOT (the public sidebar is the namespaced `page:sidebar`, a",
"different registration, and it already declares `isContainer: true`). ⇒ Whoever gives",
"`badge` or `alert` a `children` read is moving PUBLISHED CONTRACT and inherits this",
"whole trade-off; it is not the mechanical one-line fix the other 44 are.",
"",
"THE 2026-08-30 RULING (objectui#6804), AND WHY ONLY ONE OF ITS 14 TAGS IS WRITTEN",
"DOWN HERE. That ruling answered the containment question for 14 tags at once --",
"`button` plus the 13 `schema.body` readers (`badge`, `alert`, 11 `sidebar-*`) -- and",
"the answer for all 14 is the same: do NOT declare `isContainer`, because reading",
"children as a FALLBACK for `schema.label` / `schema.body` is not layout containment.",
"Only `button` has an entry below, and that is NOT an oversight: it is the only one of",
"the 14 that violates today. The other 13 put no authored child on the page, so the",
"runtime predicate already scores them as non-containers -- and a line for them here",
"would trip this file's OTHER red (`a listed tag that no longer violates`) on the day",
"it was written. They are pinned as non-violators instead, by the `schema.body` readers",
"block in the ratchet test. If objectui#6771's retirement of the `body` dialect gives",
"any of them a `children` read, that tag earns its own reasoned entry THEN.",
"",
"`since` is the date the entry was admitted, so the list carries its own history: a",
"line dated later than this file's first commit was added after the ruling and needs",
"one of its own."
],
"excluded": {
"button": {
"reason": "Ruled out of this list by objectui#6779's maintainer ruling (2026-08-29), pending a separate card. It is the ONLY public-tier member of the 45, so declaring `isContainer` would delete `Button` from the JSX scope of every `kind:'react'` page -- and it reads `children` as a LABEL FALLBACK, not as layout containment, so the containment answer is a product decision rather than a mechanical fix. The pin asserts this entry still describes reality (renders children, undeclared, public): when the separate card lands, it goes red and this entry must be resolved rather than left standing.",
"issue": "objectui#6779",
"reason": "PERMANENT, RULED EXCEPTION -- not an oversight, not pending anything. Admitted 2026-08-29 as provisional by objectui#6779, which deferred the question to a separate card; that card is objectui#6804 and the maintainer ruled it 2026-08-30: `button` does NOT declare `isContainer`. Three grounds, all re-derived on this tree. (1) SEMANTICS -- `isContainer` means LAYOUT CONTAINMENT, not `this tag renders children`. `button` reads `schema.children` only as a fallback for `schema.label` (packages/components/src/renderers/form/button.tsx, `schema.label || renderChildren(...)`), so it is not a container; declaring it would make one predicate mean two different things. (2) COST -- `button` is the only public-tier member of the 45, and `renderers/layout/react-page.tsx` skips containers when it builds the JSX scope of every `kind:'react'` page, so declaring the flag DELETES the `Button` identifier from all of them. That is an immediate, user-visible capability loss. (3) PULL -- zero measured: nothing records anyone wanting to nest layout content inside a button, so the trade is a certain cost against a hypothetical gain. ⛔ Do NOT `fix` this entry by declaring the flag, and do NOT move `button` into `undeclared` -- both override the ruling. What keeps the exception honest is the pin, which asserts all three facts it rests on (still renders children, still undeclared, still public); if any one of them changes this goes RED and the exception must be RE-RULED, not quietly re-based. Scope note for the next hand: this card deliberately did not edit that `button.tsx` line -- the `schema.body || schema.children` expression on it belongs to objectui#6771 (retirement of the `body` dialect).",
"issue": "objectui#6804",
"since": "2026-08-29"
}
},
Expand Down
Loading