From 96ed6e1f04ce055b5ab0a09e78cd96626404bf06 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 13:44:01 +0000 Subject: [PATCH 1/3] docs(closure-claims): single-source the thresholds claim and bound the block-family one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies the 2026-08-25 "bounded or derivable" ruling to the two closure claims left open on objectui#6186 after claim 1 landed as PR #6195. Claim 2 (`thresholds`) was written twice — in the plugin-dashboard page and in the sdui-parser census header — and two copies of one closure claim drift apart independently. The census header is now the canonical statement and the page points at it. Single-sourcing makes the surviving copy load-bearing, so it is also DERIVED: a new gate re-scans every JS/TS file git tracks for an access to a key of that name, with positive and negative controls, and a population that refuses to collapse on an empty walk. Claim 3 (block-schema) was a whole-tree negative that measurement showed to be false as written: `PageSchema.slots` and `PageNodeSchema.template` both have live runtime readers under a different vocabulary. Bounded to the block family's own keys, which is what the page's actual point survives. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe --- content/docs/blocks/block-schema.mdx | 12 +- content/docs/plugins/plugin-dashboard.mdx | 5 +- .../src/dashboard-widget-options.ts | 33 +- ...nconsumed-widget-option-claim-6186.test.ts | 287 ++++++++++++++++++ 4 files changed, 327 insertions(+), 10 deletions(-) create mode 100644 scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts diff --git a/content/docs/blocks/block-schema.mdx b/content/docs/blocks/block-schema.mdx index d0baf1fb95..36c285b727 100644 --- a/content/docs/blocks/block-schema.mdx +++ b/content/docs/blocks/block-schema.mdx @@ -21,9 +21,15 @@ whole implementation: `packages/types/src/index.ts`. They are discriminants of the interfaces below, **not renderable node types**. Putting one into a `children` array gives you the renderer's `OBJUI-001` "Unknown component type" panel. -- **Nothing reads `slots`, `slotContent` or `template` at runtime.** There is no block - expander, no block library UI and no block editor in this repository. The keys are - declared and validated, and that is all. +- **Nothing expands a block: `BlockSchema.slots`, `BlockInstanceSchema.slotContent` and + `BlockSchema.template` have no reader.** The population that would hold one is named and + empty — there is no block expander, no block library UI and no block editor. These + interfaces and their Zod validators are the whole implementation. Bounded to the block + family's **own** keys deliberately: the bare words `slots` and `template` are live + vocabulary elsewhere in this repository and *are* read at runtime there — + `PageSchema.slots` drives slotted record pages (`buildDefaultPageSchema` in + `@object-ui/plugin-detail`), and `PageNodeSchema.template` selects a page layout from + `TEMPLATE_REGISTRY` in `@object-ui/components`. Neither goes near `BlockSchema`. So use these types to **describe** a block (author tooling, a marketplace payload, a validation step) and do not expect one to render. Looking for slots that work today? diff --git a/content/docs/plugins/plugin-dashboard.mdx b/content/docs/plugins/plugin-dashboard.mdx index 2699c25533..323ccf3654 100644 --- a/content/docs/plugins/plugin-dashboard.mdx +++ b/content/docs/plugins/plugin-dashboard.mdx @@ -319,8 +319,9 @@ from widget `options`. In particular, on a dataset-bound gauge: - `options.format` is **not read** — declare the format on the dataset measure instead; -- `options.thresholds` is **not read** — no renderer in this repository reads - a `thresholds` key; +- `options.thresholds` is **not read** — the census header in + `packages/sdui-parser/src/dashboard-widget-options.ts` is the canonical + statement of that closure claim, and a gate re-derives it on every test run; - `options.invert` is **not read** — if a measure needs to be displayed as its complement (e.g. a compliance rate stored as a violation rate), add a derived measure to the dataset (`derived: { op: 'ratio', … }`) and bind the diff --git a/packages/sdui-parser/src/dashboard-widget-options.ts b/packages/sdui-parser/src/dashboard-widget-options.ts index 079fb67aa5..06d04e2d82 100644 --- a/packages/sdui-parser/src/dashboard-widget-options.ts +++ b/packages/sdui-parser/src/dashboard-widget-options.ts @@ -35,11 +35,34 @@ * legal metadata, so it is in the accepted set even though the dataset-bound * render path does not currently display it. * - * Notably NOT consumed anywhere in this repository: `thresholds` (zero read - * sites repo-wide) and `format` — the dataset-bound value is formatted with - * the MEASURE's own metadata (`measureField(...).format`, from the dataset - * definition), not with `options.format`. Both were widely believed to work; - * both draw this warning, which is the point. + * Notably NOT consumed on the path a widget really renders through: + * `thresholds` and `format`. Both were widely believed to work; both draw this + * warning, which is the point. They are NOT the same kind of claim, and since + * the 2026-08-25 maintainer ruling (batch adjudication close-out, decision B1 — + * a closure claim must be BOUNDED or DERIVABLE, and a claim written in two + * places is single-sourced) they are written separately (objectui#6186): + * + * `thresholds` — zero read sites repo-wide. This paragraph is the CANONICAL + * statement of that closure claim. `content/docs/plugins/plugin-dashboard.mdx` + * used to assert it a second time in its own words; two copies of one + * closure claim drift apart independently and neither knows when the other + * stopped being true, so the page now POINTS here rather than restating it. + * Single-sourcing is what makes this copy load-bearing, so it is DERIVED + * rather than trusted: + * `scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts` re-scans + * every JS/TS file git tracks for an access to a key of that name and fails + * if one appears. Land a renderer that reads it and this module goes red in + * the same run. + * `format` — not read ON THE DATASET-BOUND PATH; the value is formatted with + * the MEASURE's own metadata (`measureField(...).format`, from the dataset + * definition), not from the widget's bag. Deliberately a BOUNDED claim and + * not a repo-wide one: `format` is a live key in other vocabularies, so a + * repo-wide scan would red on a TRUE claim — and an assertion that reds on + * legitimate code gets deleted by the next person who hits it, which puts + * the claim back where it started. Leg 2 of + * `__tests__/dashboard-widget-options-census.test.ts` derives exactly this + * bound: the DatasetWidget read set equals the declared set, and neither + * key is in it. * * ## Scope — where the warning deliberately does NOT fire * diff --git a/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts b/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts new file mode 100644 index 0000000000..d59fe19be0 --- /dev/null +++ b/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts @@ -0,0 +1,287 @@ +import { describe, expect, it } from 'vitest'; +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Plain-JS CI helper. Its types are INFERRED from the .mjs source by +// `tsconfig.scripts.json` (`allowJs`), so no `@ts-expect-error` here — +// re-adding one is itself an error (TS2578). See objectui#3494. +import { blank, maskComments, scanSource } from '../js-comment-mask.mjs'; + +/** + * ObjectUI — the `thresholds` closure claim, re-derived (objectui#6186 claim 2) + * + * ## What this file is for + * + * `packages/sdui-parser/src/dashboard-widget-options.ts` warns on a dashboard + * widget `options` key that reaches no renderer. Its census header names + * `thresholds` as the headline case, and the reason the warning is allowed to + * fire on it is a CLOSURE CLAIM: nothing in this repository reads a + * `thresholds` key. That claim used to be written TWICE — once in the census + * header and once, in its own words, on + * `content/docs/plugins/plugin-dashboard.mdx`. Two copies of one closure claim + * drift apart independently, and neither copy knows when the other stops being + * true. + * + * The 2026-08-25 maintainer ruling (batch adjudication close-out, decision B1) + * settled what a doc may assert about a population it does not own: **bounded + * or derivable**. A closure claim must either name a bounded population, or be + * backed by a gate that re-derives it. A claim written in two places is + * single-sourced — one copy states it, the other points. + * + * So: the census header is now the CANONICAL statement, the page points at it, + * and single-sourcing is exactly what makes the surviving copy load-bearing. + * This file is the other half of the ruling — it re-derives the claim from + * source on every test run instead of trusting the paragraph that states it. + * + * ⛔ Why not a `toContain` pin on the prose. That is the defect objectui#6186 + * filed one level up: a test asserting a sentence is PRESENT cannot assert the + * sentence is TRUE, and a green presence pin reads as coverage of the claim it + * quotes. Land a renderer that reads the key tomorrow and a presence pin stays + * green while the census, the warning and the page all turn false together. + * + * ## What the instrument can and cannot see — read before trusting a verdict + * + * It is a TEXT scan over comment- and literal-masked code, so it answers "does + * any site in this repository access a key named `thresholds`". It matches the + * three spellings an access can take — member (`x.thresholds`, including + * optional chaining), computed with a literal key (`x['thresholds']`), and + * destructured (`const { thresholds } = x`). + * + * Two stated biases, both deliberate: + * + * - **A member-access WRITE matches too** (`x.thresholds = 1`). The claim is + * about reads, so this is the red-on-a-true-claim direction — accepted + * because a site that puts the key onto an object is something a human + * should look at before the warning keeps firing, and because no such site + * exists in this tree. + * - **A computed access through a VARIABLE key is invisible** (`x[k]` where + * `k === 'thresholds'`). Nothing can see that textually. The census test's + * leg 2 covers the one file where it would matter by REFUSING the shape: + * `packages/sdui-parser/src/__tests__/dashboard-widget-options-census.test.ts` + * fails loudly if `DatasetWidget.tsx` ever gains a computed access, a spread + * or a destructuring of the options bag. + * + * ⚠️ TWO masks, because the three spellings need different ones — measured, + * not assumed: the positive control below caught this file getting it wrong on + * the first run. A member access and a destructuring are CODE, so both are read + * off source with comments AND string literals blanked; a mention of the key + * inside a string is prose there. But a computed access spells its key AS a + * string literal (`x['thresholds']`), so blanking literals erases the very + * thing being looked for — that leg reads source with COMMENTS ONLY blanked. + * Its residual blind spot, stated: a whole computed access quoted inside a + * string literal would be counted. That is the red-on-a-true-claim direction, + * it is rare, and it is why the fixture table below spells its self-reference + * case as a member access. + * + * Masking is what lets this scan read its own negative-control table, the + * census header and this very docblock — all of which spell the key — without + * reddening on any of them. + * + * ## Relationship to the census test + * + * They derive different halves and neither does the other's job: + * + * - the census test re-derives the CONSUMED set (what the declared keys are, + * and what `DatasetWidget.tsx` really reads) — a claim about one render path; + * - this file re-derives the UNCONSUMED claim for `thresholds` — a claim about + * the whole repository, which is why it needs the whole repository. + * + * ⚠️ `format` and `invert`, the census's other two unconsumed keys, are + * deliberately NOT scanned this way. Both are live keys in other vocabularies + * (`measureField(...).format` is how a dataset-bound value is really formatted), + * so a repo-wide bare scan would red on true claims — the failure the dispatch + * on this card called the load-bearing risk, because an assertion that reds on + * legitimate code gets deleted by the next person who hits it, and the claim + * goes back to unguarded. Their claim is BOUNDED to the dataset-bound path + * instead, and the census test's leg 2 derives that bound. + */ + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +const CENSUS_MODULE = 'packages/sdui-parser/src/dashboard-widget-options.ts'; +const DASHBOARD_PAGE = 'content/docs/plugins/plugin-dashboard.mdx'; + +/** + * Comment- and literal-blanked source — the repo's ONE answer to + * "is this span code, or prose?" (`scripts/js-comment-mask.mjs`). Masking only + * ever REMOVES text, so a site found in the masked code implies the raw file + * carries it. + */ +function codeOnly(source: string): string { + const { comment, literal } = scanSource(source); + const flags = new Uint8Array(source.length); + for (let i = 0; i < source.length; i++) flags[i] = comment[i] || literal[i]; + return blank(source, flags); +} + +/** + * Every site in `source` that accesses a key named `thresholds`, reported as + * `line: what`. Empty means the census claim holds for this file. + */ +function thresholdsKeySites(source: string): string[] { + const code = codeOnly(source); + const lineOf = (index: number) => code.slice(0, index).split('\n').length; + const sites: string[] = []; + + // Member access, `.thresholds` and `?.thresholds` alike. The trailing `\b` + // keeps `thresholdsCount` out; requiring the dot immediately before the name + // keeps `alertThresholds` and every other camelCase suffix spelling out. + for (const m of code.matchAll(/\.\s*thresholds\b/g)) { + sites.push(`${lineOf(m.index ?? 0)}: member access \`${m[0].replace(/\s+/g, '')}\``); + } + + // Computed access with a literal key — read off COMMENT-masked source, where + // string literals survive. `codeOnly` would blank the key itself and this leg + // could never fire (it silently did not, until the positive control caught + // it). A variable key (`x[k]`) is invisible to any text scan and is declared + // as such in this file's header. + const commentMasked = maskComments(source); + const lineOfRaw = (index: number) => commentMasked.slice(0, index).split('\n').length; + for (const m of commentMasked.matchAll(/\[\s*(['"])thresholds\1\s*\]/g)) { + sites.push(`${lineOfRaw(m.index ?? 0)}: computed access \`['thresholds']\``); + } + + // Destructuring. The trailing `=` is what separates a destructuring PATTERN + // (`const { thresholds } = bag` — a read) from an object LITERAL + // (`{ thresholds: [...] }` — a write, which authors the key rather than + // consuming it, and which this repository's own test fixtures really do). + // `[^{}]*` keeps the match inside one brace level, so a nested object cannot + // be walked into by accident. + for (const m of code.matchAll(/\{[^{}]*\bthresholds\b[^{}]*\}\s*=/g)) { + sites.push(`${lineOf(m.index ?? 0)}: destructured from a bag`); + } + + return sites; +} + +describe('objectui#6186 claim 2 — the matcher discriminates before it is trusted', () => { + // Fixtures rather than the tree, so both controls stay decidable when the + // source moves. The POSITIVE control proves the assertion below can fail at + // all; an assertion that cannot fail is the vacuous-green shape this whole + // card is about. + it('fires on every spelling a real access can take', () => { + for (const source of [ + 'const t = widget.options.thresholds;', + 'const t = widget.options?.thresholds;', + "const t = options['thresholds'];", + 'const t = options["thresholds"];', + 'const { thresholds } = widget.options;', + 'const { limit, thresholds } = opts;', + 'function render({ thresholds } = {}) { return thresholds; }', + 'if (widget.options.thresholds) return null;', + ]) { + expect(thresholdsKeySites(source), source).not.toEqual([]); + } + }); + + // ⭐ The load-bearing half. Every negative below is a spelling this repository + // really writes, cited where it lives. An assertion that red on any of them + // would be deleted by the first person who hit it, and the claim would be + // back to unguarded — which is worse than never having written this file. + it('stays silent on every near-spelling and prose mention this tree really carries', () => { + for (const source of [ + '/** Alert thresholds */', // packages/types/src/data-protocol.ts:1590 + '// against configurable thresholds. Violations are recorded, deduplicated', // react/src/hooks/usePerformanceBudget.ts:128 + '// the total from `result.total` and offers the banner at the same thresholds.', // plugin-list ListView.crossPageSelectAll.test.tsx:222 + 'const options = { thresholds: [{ value: 0.95, color: '+ "'success'" + ' }] };', // sdui-parser dashboard-widget-options.test.ts:61 + "expect(keys).toEqual(['format', 'invert', 'thresholds']);", // sdui-parser dashboard-widget-options.test.ts:98 + 'const coverage = { thresholds: { lines: 40, functions: 33 } };', // vitest.config.mts coverage config + "const note = 'enforces the configured coverage thresholds over it';", // scripts/dependabot-merge-gate.mjs:203 + 'const t = budget.alertThresholds;', // camelCase suffix spelling + 'const t = cfg.thresholdsCount;', // prefix spelling + 'const t = thresholds;', // a bare local, not a KEY access + 'export function apply(thresholds: number[]) { return thresholds.length; }', + '// widget.options.thresholds is not read by any renderer', // ⭐ self-reference: this repo's own prose + "const fixture = 'const t = widget.options.thresholds;';", // ⭐ self-reference: a fixture table + ]) { + expect(thresholdsKeySites(source), source).toEqual([]); + } + }); +}); + +describe('objectui#6186 claim 2 — the claim itself, re-derived from source', () => { + it('nothing in this repository accesses a `thresholds` key', () => { + // SCANNED POPULATION, stated because the claim says "in this repository": + // every JS/TS-family file git tracks — `.ts`, `.tsx`, `.mts`, `.cts`, `.js`, + // `.mjs`, `.cjs`, `.jsx`. 3,743 files on the tree this was written against. + // + // DERIVED, not listed, and derived from the same configuration the repo's + // other whole-tree gates read: `node_modules`, `dist` and every build output + // are untracked and so are out by construction. + // `scripts/check-control-bytes.mjs` reads this repository the same way. + // + // The JS half is in on purpose. The census header's claim is about the whole + // repository, not about its TypeScript; scanning only `.ts` would make the + // gate assert LESS than the prose while looking like it covers it, which is + // the exact defect objectui#6186 filed. Population and claim have to stay + // co-extensive — narrow one, narrow the other in the same change. + const tracked = execFileSync('git', ['ls-files', '-z'], { + cwd: repoRoot, + encoding: 'utf8', + maxBuffer: 64 * 1024 * 1024, + }) + .split('\0') + .filter((rel) => /\.(?:[cm]?[jt]s|[jt]sx)$/.test(rel)); + + // The walk cannot collapse quietly. An empty population makes the assertion + // below vacuous and green forever — a scan that finds nothing reads exactly + // like a scan that found nothing wrong, which is the failure shape this card + // is about, one level up. + expect(tracked.length, 'the source walk found no JS/TS files at all').toBeGreaterThan(1000); + + const sites: string[] = []; + for (const rel of tracked) { + const source = fs.readFileSync(path.join(repoRoot, rel), 'utf8'); + // Prefilter on the raw bytes. Masking only ever REMOVES text, so a site in + // the masked code implies the raw file carries the name. + if (!source.includes('thresholds')) continue; + for (const site of thresholdsKeySites(source)) sites.push(`${rel}:${site}`); + } + + expect( + sites, + `${CENSUS_MODULE} tells the reader that nothing in this repository reads a ` + + '`thresholds` key, and the unconsumed-widget-option warning fires on that basis. ' + + `These access one, so either the census or the access has to go:\n${sites.join('\n')}`, + ).toEqual([]); + }); +}); + +describe('objectui#6186 claim 2 — the claim is single-sourced, and the pointer resolves', () => { + const page = fs.readFileSync(path.join(repoRoot, DASHBOARD_PAGE), 'utf8'); + const census = fs.readFileSync(path.join(repoRoot, CENSUS_MODULE), 'utf8'); + + /** + * A docblock WRAPS. `toContain` over raw text therefore fails when someone + * reflows a paragraph, which is a false red on an unchanged claim — so the + * comment markers come off and the whitespace collapses first, and what is + * asserted is the sentence rather than its line breaks. + */ + const prose = (source: string) => source.replace(/^\s*\*\s?/gm, '').replace(/\s+/g, ' '); + + it('the canonical copy is the census header, and it still states the claim', () => { + // Referential integrity, not a sentence pin: the page below points AT this + // file, so the claim vanishing from it would leave the pointer aimed at + // nothing while every test stayed green. + expect(prose(census)).toContain('CANONICAL statement of that closure claim'); + expect(prose(census)).toContain('zero read sites repo-wide'); + }); + + it('the page points at the canonical copy instead of restating it', () => { + // The ruling: a claim written in two places is single-sourced — one copy + // states it, the other points. What must not come back is a SECOND + // independent wording of the closure claim on the page, which is what + // objectui#6186 filed: the two copies drift apart and neither knows. + expect(page).toContain(CENSUS_MODULE); + expect(page, 'the page is restating the closure claim in its own words again').not.toMatch( + /no renderer in this repository reads/i, + ); + }); + + it('the pointer names a file that really exists', () => { + // A path in prose is a claim too. This one is cheap to re-derive, so it is. + expect(fs.existsSync(path.join(repoRoot, CENSUS_MODULE))).toBe(true); + }); +}); From d31ea5550209c27c37dbbc15b4bda241da7a14dc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 13:46:51 +0000 Subject: [PATCH 2/3] test(closure-claims): the computed-access leg must not count a QUOTED access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gate reddened on its own fixture table and diagnostic message the moment it was committed — which is the moment a tracked-files population first contained it. Keeping string literals visible is what lets that leg see `x['thresholds']` at all, but it also makes an access merely quoted inside a string look identical to a real one. `scanSource`'s literal flags settle the difference exactly: in real code the bracket is code and the key is the literal; in a quoted access the bracket is literal too. The leg now requires the bracket itself to be code, and the two shapes this file really writes are pinned as negative controls. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe --- ...nconsumed-widget-option-claim-6186.test.ts | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts b/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts index d59fe19be0..e5b57c816d 100644 --- a/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts +++ b/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts @@ -70,10 +70,14 @@ import { blank, maskComments, scanSource } from '../js-comment-mask.mjs'; * inside a string is prose there. But a computed access spells its key AS a * string literal (`x['thresholds']`), so blanking literals erases the very * thing being looked for — that leg reads source with COMMENTS ONLY blanked. - * Its residual blind spot, stated: a whole computed access quoted inside a - * string literal would be counted. That is the red-on-a-true-claim direction, - * it is rare, and it is why the fixture table below spells its self-reference - * case as a member access. + * Keeping literals visible costs one thing back, and it is paid rather than + * declared: an access merely QUOTED inside a string (`"options['thresholds']"`) + * looks identical to a real one. The literal flags settle it — in real code the + * `[` is code and the key is the literal; in a quoted access the `[` is literal + * too — so that leg requires the bracket itself to be code. This is not + * theoretical: the gate reddened on its OWN fixture table and diagnostic + * message the moment it was committed, which is the moment a tracked-files + * population first contained it. * * Masking is what lets this scan read its own negative-control table, the * census header and this very docblock — all of which spell the key — without @@ -122,7 +126,9 @@ function codeOnly(source: string): string { */ function thresholdsKeySites(source: string): string[] { const code = codeOnly(source); - const lineOf = (index: number) => code.slice(0, index).split('\n').length; + // Every mask BLANKS rather than deletes, so offsets are shared across all + // three views and one line lookup serves them all. + const lineOf = (index: number) => source.slice(0, index).split('\n').length; const sites: string[] = []; // Member access, `.thresholds` and `?.thresholds` alike. The trailing `\b` @@ -137,10 +143,21 @@ function thresholdsKeySites(source: string): string[] { // could never fire (it silently did not, until the positive control caught // it). A variable key (`x[k]`) is invisible to any text scan and is declared // as such in this file's header. + // + // ⭐ The bracket must ITSELF be code. Keeping literals visible is what lets + // this leg see `x['thresholds']`, but it also lets it see an access merely + // QUOTED inside a string — which this very file does, in its fixture table and + // in the diagnostic message three lines below. `scanSource`'s literal flags + // answer the difference exactly: in real code the `[` is code and the KEY is + // the literal; in a quoted access the `[` is literal too. Measured, not + // assumed — the gate went red on its own source the moment it became a + // tracked file, which is also when it first entered its own population. const commentMasked = maskComments(source); - const lineOfRaw = (index: number) => commentMasked.slice(0, index).split('\n').length; + const { literal } = scanSource(source); for (const m of commentMasked.matchAll(/\[\s*(['"])thresholds\1\s*\]/g)) { - sites.push(`${lineOfRaw(m.index ?? 0)}: computed access \`['thresholds']\``); + const at = m.index ?? 0; + if (literal[at]) continue; + sites.push(`${lineOf(at)}: computed access \`['thresholds']\``); } // Destructuring. The trailing `=` is what separates a destructuring PATTERN @@ -195,6 +212,8 @@ describe('objectui#6186 claim 2 — the matcher discriminates before it is trust 'export function apply(thresholds: number[]) { return thresholds.length; }', '// widget.options.thresholds is not read by any renderer', // ⭐ self-reference: this repo's own prose "const fixture = 'const t = widget.options.thresholds;';", // ⭐ self-reference: a fixture table + 'const msg = `computed access [\'thresholds\']`;', // ⭐ a template QUOTING an access + "const fixture2 = \"const t = options['thresholds'];\";", // ⭐ this file's own fixture shape ]) { expect(thresholdsKeySites(source), source).toEqual([]); } From e87969072212fc17f0c3bf49857c43ec5fa28869 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 13:49:04 +0000 Subject: [PATCH 3/3] chore(changeset): declare the closure-claim work as releasing nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only published-source edit is a comment header in @object-ui/sdui-parser's census module — all 33 changed lines are block-comment lines, no exported symbol or runtime behaviour moves. Empty frontmatter is the form check-changeset-presence names for that case. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe --- .changeset/closure-claims-bounded-or-derivable.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/closure-claims-bounded-or-derivable.md diff --git a/.changeset/closure-claims-bounded-or-derivable.md b/.changeset/closure-claims-bounded-or-derivable.md new file mode 100644 index 0000000000..ccf0743e1b --- /dev/null +++ b/.changeset/closure-claims-bounded-or-derivable.md @@ -0,0 +1,13 @@ +--- +--- + +Documentation and gate-only change (objectui#6186). The `thresholds` closure claim +carried by `@object-ui/sdui-parser`'s unconsumed-widget-option census was written +twice — in the census header and in the plugin-dashboard docs page — and is now +single-sourced onto the census header, with a new gate re-deriving it from source +on every test run. The block-schema page's whole-tree negative is bounded to the +block family's own keys. + +The only edit to published source is the census module's comment header: no +exported symbol, runtime behaviour or emitted diagnostic changes, so this releases +nothing.