diff --git a/ui/QUICK_REFERENCE.md b/ui/QUICK_REFERENCE.md index cbd2937dd0f..b0fba44e522 100644 --- a/ui/QUICK_REFERENCE.md +++ b/ui/QUICK_REFERENCE.md @@ -82,7 +82,7 @@ pnpm changeset publish # Publish to npm (CI only) | Path | Purpose | | --- | --- | -| `packages/*` | 35 published packages (`@object-ui/*`), plus the private `vscode-extension`, `test-support` (test-only, never released), `runner` (workspace demo app, never released) and `app-shell` (the Console's shell and views, no longer published after 18.2.0) | +| `packages/*` | The published `@object-ui/*` packages, plus the private `vscode-extension`, `test-support` (test-only, never released), `runner` (workspace demo app, never released) and `app-shell` (the Console's shell and views, no longer published after 18.2.0) | | `apps/console` | Full ObjectUI console app (Vite + React) | | `apps/site` | Public docs site at (fumadocs) | | `examples/*` | Runnable examples and the schema catalog — see [`examples/README.md`](./examples/README.md) | diff --git a/ui/examples/schema-catalog/test/badge-demo-label-6829.test.tsx b/ui/examples/schema-catalog/test/badge-demo-label-6829.test.tsx index 517ecc6cb01..3603fcb207f 100644 --- a/ui/examples/schema-catalog/test/badge-demo-label-6829.test.tsx +++ b/ui/examples/schema-catalog/test/badge-demo-label-6829.test.tsx @@ -119,6 +119,7 @@ import { render } from '@testing-library/react'; import '@object-ui/components'; import { SchemaRenderer, toRenderableSchema } from '@object-ui/react'; import { allExamples, getExample } from '../src/index.js'; +import { CATALOG_ENTRY_FLOOR } from './catalog-entry-floor.js'; type Json = Record; @@ -245,10 +246,12 @@ const containers = entries.flatMap((entry) => describe('catalog corpus: every `badge` node draws its authored `label` (objectui#6829 arm A)', () => { it('the walk is not vacuous — a broken collector would pass every case below', () => { // Floors, not equalities: catalog growth must not fail this file, only a - // walk that stopped working. - expect(entries.length).toBeGreaterThanOrEqual(431); - expect(badges.length).toBeGreaterThanOrEqual(40); - expect(new Set(badges.map((b) => b.where.split('/')[0])).size).toBeGreaterThanOrEqual(12); + // walk that stopped working. About two thirds of the counts measured on + // objectstack 6a7c0fe3 (40 badges in 12 categories), so deleting an entry + // does not fail it either. + expect(entries.length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); + expect(badges.length).toBeGreaterThanOrEqual(27); + expect(new Set(badges.map((b) => b.where.split('/')[0])).size).toBeGreaterThanOrEqual(8); // …and it sees the seven nodes this card is about, by location. expect(badges.map((b) => b.where)).toEqual(expect.arrayContaining(REAUTHORED_NODES)); }); diff --git a/ui/examples/schema-catalog/test/card-demo-content-6788.test.tsx b/ui/examples/schema-catalog/test/card-demo-content-6788.test.tsx index 8c0097be64b..18fcad839eb 100644 --- a/ui/examples/schema-catalog/test/card-demo-content-6788.test.tsx +++ b/ui/examples/schema-catalog/test/card-demo-content-6788.test.tsx @@ -124,6 +124,7 @@ import '@object-ui/components'; import { SchemaRenderer, toRenderableSchema } from '@object-ui/react'; import { CardSchema } from '@object-ui/types/zod'; import { allExamples } from '../src/index.js'; +import { CATALOG_ENTRY_FLOOR } from './catalog-entry-floor.js'; type Json = Record; @@ -213,11 +214,13 @@ function unreadKeys(node: Json, allowed: readonly string[]): string[] { describe('catalog corpus: every `card` node authors keys `ui:card` reads (objectui#6788)', () => { it('the walk is not vacuous — a broken collector would pass every case below', () => { - // Measured on d06059f24; these are floors, not equalities, so growth of - // the catalog does not fail the file — only a walk that stopped working. - expect(entries.length).toBeGreaterThanOrEqual(431); - expect(cards.length).toBeGreaterThanOrEqual(93); - expect(new Set(cards.map((c) => c.where.split('/')[0])).size).toBeGreaterThanOrEqual(19); + // Floors, not equalities, so growth of the catalog does not fail the file — + // only a walk that stopped working. About two thirds of the counts measured + // on objectstack 6a7c0fe3 (93 cards in 19 categories), so deleting an entry + // does not fail it either. + expect(entries.length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); + expect(cards.length).toBeGreaterThanOrEqual(62); + expect(new Set(cards.map((c) => c.where.split('/')[0])).size).toBeGreaterThanOrEqual(13); }); it('every authored key is DECLARED by the shipped CardSchema', () => { diff --git a/ui/examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx b/ui/examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx index 3afe8018f83..50b4ea42a45 100644 --- a/ui/examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx +++ b/ui/examples/schema-catalog/test/catalog-authored-key-6805-6806.test.tsx @@ -117,6 +117,7 @@ import { render } from '@testing-library/react'; import '@object-ui/components'; import { SchemaRenderer, toRenderableSchema } from '@object-ui/react'; import { allExamples } from '../src/index.js'; +import { CATALOG_ENTRY_FLOOR } from './catalog-entry-floor.js'; type Json = Record; @@ -150,7 +151,8 @@ const RENDERERS = [ readKeys: ['type', 'orientation', 'height', 'width', 'className', 'children'], contentKey: 'children', issue: 'objectui#6805', - minNodes: 7, + // About two thirds of the 7 measured on objectstack 6a7c0fe3. + minNodes: 5, }, { type: 'badge', @@ -161,7 +163,8 @@ const RENDERERS = [ readKeys: ['type', 'label', 'variant', 'className', 'body'], contentKey: 'label', issue: 'objectui#6806', - minNodes: 40, + // About two thirds of the 40 measured on objectstack 6a7c0fe3. + minNodes: 27, }, ] as const; @@ -256,8 +259,9 @@ const nodesOf = (type: string): Located[] => describe('catalog corpus: every node authors keys ITS renderer reads (objectui#6805 + objectui#6806)', () => { it('the walk is not vacuous — a broken collector would pass every case below', () => { // Floors, not equalities: catalog growth must not fail this file, only a - // walk that stopped working. - expect(entries.length).toBeGreaterThanOrEqual(431); + // walk that stopped working — and at about two thirds of the measured + // counts, so deleting an entry does not fail it either. + expect(entries.length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); for (const renderer of RENDERERS) { expect(nodesOf(renderer.type).length).toBeGreaterThanOrEqual(renderer.minNodes); } diff --git a/ui/examples/schema-catalog/test/catalog-entry-floor.ts b/ui/examples/schema-catalog/test/catalog-entry-floor.ts new file mode 100644 index 00000000000..3563dd2957f --- /dev/null +++ b/ui/examples/schema-catalog/test/catalog-entry-floor.ts @@ -0,0 +1,15 @@ +/** + * The ONE anti-vacuity floor for the size of the catalog (`allExamples()`), read + * by every test here that guards its own sweep against an empty corpus. + * + * A floor, not a census: it answers "did the collector find a corpus at all?", + * so catalog growth never moves it and neither does deleting an entry. It sits + * at about two thirds of the 432 entries measured on objectstack `origin/main` + * `6a7c0fe3` (the `vite-dts-wiring-ratchet` precedent: measured 22, floor 15). + * Before it existed the same population was pinned at three different values + * across eight call sites, the tightest one entry under the measured count. + * + * Re-measure only when the corpus has shrunk to near it, and then move it to + * about two thirds of the new reading. ⛔ Never raise it to track growth. + */ +export const CATALOG_ENTRY_FLOOR = 288; diff --git a/ui/examples/schema-catalog/test/catalog-gallery-render.test.tsx b/ui/examples/schema-catalog/test/catalog-gallery-render.test.tsx index 5dbbda46d17..061bfd35f1c 100644 --- a/ui/examples/schema-catalog/test/catalog-gallery-render.test.tsx +++ b/ui/examples/schema-catalog/test/catalog-gallery-render.test.tsx @@ -149,6 +149,7 @@ import { SchemaRenderer, SchemaRendererContext, toRenderableSchema } from '@obje import fs from 'node:fs'; import path from 'node:path'; import { allExamples } from '../src/index.js'; +import { CATALOG_ENTRY_FLOOR } from './catalog-entry-floor.js'; /** * The repo root, derived from THIS FILE's own location — never from @@ -646,7 +647,7 @@ describe('objectui#4616 — every catalog entry renders in the docs gallery', () * size and the registration set the sweep depends on. */ it('the corpus is populated and every package the census needs is loaded', () => { - expect(entries.length).toBeGreaterThanOrEqual(423); + expect(entries.length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); // One type per package below, chosen as the one the census actually needed. for (const type of [ 'dashboard', @@ -1345,7 +1346,9 @@ describe('objectui#6025 — the gallery DECLARES the packages its entries need', expect( PLUGIN_CATEGORIES.length, 'no plugin categories were derived, so this case would be vacuous', - ).toBeGreaterThanOrEqual(13); + // About two thirds of the 13 measured on objectstack 6a7c0fe3: retiring a + // plugin package (and its category) must not red an anti-vacuity floor. + ).toBeGreaterThanOrEqual(9); const undeclared = PLUGIN_CATEGORIES.map((category) => `@object-ui/${category}`).filter( (pkg) => !HOST_PACKAGES.includes(pkg), diff --git a/ui/examples/schema-catalog/test/component-fixture-declared-keys.test.ts b/ui/examples/schema-catalog/test/component-fixture-declared-keys.test.ts index afd335f53a4..94d08b49b2a 100644 --- a/ui/examples/schema-catalog/test/component-fixture-declared-keys.test.ts +++ b/ui/examples/schema-catalog/test/component-fixture-declared-keys.test.ts @@ -87,6 +87,7 @@ import { } from '@object-ui/types/zod'; import { enumOptions } from '@object-ui/test-support'; import { allExamples, getExample } from '../src/index.js'; +import { CATALOG_ENTRY_FLOOR } from './catalog-entry-floor.js'; type Json = Record; @@ -280,7 +281,7 @@ describe('catalog corpus: no fixture hangs an action object off a handler key (o expect(handlers.map((h) => h.where)).toContain( 'components-feedback-toaster/with-toast-trigger.children[0].onClick', ); - expect(entries.length).toBeGreaterThan(400); + expect(entries.length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); }); it('every handler value in the corpus is a string expression, never an action object', () => { @@ -565,7 +566,7 @@ describe('catalog corpus: every menubar item uses the declared MenuItem spelling ]), ); expect(items).toHaveLength(13); - expect(allExamples().length).toBeGreaterThan(400); + expect(allExamples().length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); }); it('the walker descends into submenu `children` — synthetic positive control', () => { @@ -773,7 +774,7 @@ describe('catalog corpus: no overlay-menu item authors the undeclared `value` ke // below. 25 items = menubar 13 + basic-context-menu 5 + basic-dropdown-menu // 4 + with-icons 3. expect(items).toHaveLength(25); - expect(allExamples().length).toBeGreaterThan(400); + expect(allExamples().length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); // One representative position per container shape, including the deeper // `menus[].items[]` nesting only `menubar` has. @@ -935,11 +936,12 @@ describe('catalog corpus: no `select` option authors the undeclared `type` key ( it('the sweep reaches every select option in the corpus — non-vacuity', () => { // Floors, not equalities: catalog growth must not fail this file, only a - // walker that stopped working. 66 options on 19 select nodes across 7 - // categories at `b6e83be6a`. - expect(options.length).toBeGreaterThanOrEqual(66); - expect(new Set(options.map((o) => o.where.split('/')[0])).size).toBeGreaterThanOrEqual(7); - expect(allExamples().length).toBeGreaterThan(400); + // walker that stopped working. About two thirds of the 66 options across 7 + // categories measured on objectstack 6a7c0fe3, so deleting an entry does + // not fail it either. + expect(options.length).toBeGreaterThanOrEqual(44); + expect(new Set(options.map((o) => o.where.split('/')[0])).size).toBeGreaterThanOrEqual(5); + expect(allExamples().length).toBeGreaterThanOrEqual(CATALOG_ENTRY_FLOOR); // One representative position per authoring shape, including the two // nesting depths a shallow walker would silently miss: a select inside a diff --git a/ui/scripts/__tests__/check-dist-completeness.test.ts b/ui/scripts/__tests__/check-dist-completeness.test.ts index 9b533c9e6a7..3a50724a399 100644 --- a/ui/scripts/__tests__/check-dist-completeness.test.ts +++ b/ui/scripts/__tests__/check-dist-completeness.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { uiTurboConfigPath } from '../workspace-root.mjs'; +import { UI_TSC_BUILT_PACKAGE_FLOOR } from './helpers/ui-package-population.mjs'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; @@ -269,7 +270,9 @@ describe('this repository', () => { const tscPackages = discoverTscPackages(repoRoot) as Array<{ name: string; dir: string }>; it('finds the tsc-built packages', () => { - expect(tscPackages.length).toBeGreaterThanOrEqual(11); + // A floor against an empty walk, not a census: about two thirds of the 11 + // measured on objectstack 6a7c0fe3, so retiring a package leaves it green. + expect(tscPackages.length).toBeGreaterThanOrEqual(UI_TSC_BUILT_PACKAGE_FLOOR); }); it.each(tscPackages.map((p) => [p.name, p.dir] as const))( diff --git a/ui/scripts/__tests__/check-doc-links.test.ts b/ui/scripts/__tests__/check-doc-links.test.ts index 98061a4e1cf..32efc809c6e 100644 --- a/ui/scripts/__tests__/check-doc-links.test.ts +++ b/ui/scripts/__tests__/check-doc-links.test.ts @@ -22,6 +22,7 @@ import { stripCode, } from '../check-doc-links.mjs'; import { GithubSlugger as VendoredSlugger, slug as vendoredSlug } from '../github-slug.mjs'; +import { UI_PUBLISHED_PACKAGE_FLOOR } from './helpers/ui-package-population.mjs'; /** * objectui#3479 — the behaviour test for `scripts/check-doc-links.mjs`. @@ -514,16 +515,18 @@ describe('the repo it guards', () => { expect(scanned['README.md']).toBe(1); expect(scanned['CONTRIBUTING.md']).toBe(1); expect(scanned['ROADMAP.md']).toBe(1); - // Three READMEs since the two host-console examples were deleted (#25922): - // the index, hello-world and schema-catalog. - expect(scanned['examples']).toBeGreaterThanOrEqual(3); + // A floor against an empty scan, not a census: three READMEs today (the + // index, hello-world and schema-catalog, measured on objectstack 6a7c0fe3), + // so deleting one example leaves it green. + expect(scanned['examples']).toBeGreaterThanOrEqual(2); expect(scanned['docs']).toBeGreaterThanOrEqual(15); expect(scanned['content/docs']).toBeGreaterThanOrEqual(100); - // objectui#3622. 38 of the 39 package directories carry a README today - // (`sdui-parser` does not), and this row is the only one whose path needs - // expanding before anything is opened — so a floor here is also the floor - // under `expandWildcard()`. - expect(scanned['packages/*/README.md']).toBeGreaterThanOrEqual(35); + // objectui#3622. This row is the only one whose path needs expanding before + // anything is opened, so a floor here is also the floor under + // `expandWildcard()`. Every published package carries a README, so the + // shared published-package floor bounds it (39 READMEs measured on + // objectstack 6a7c0fe3); deleting a package moves no floor. + expect(scanned['packages/*/README.md']).toBeGreaterThanOrEqual(UI_PUBLISHED_PACKAGE_FLOOR); // objectui#4148. Both apps carry one today; the floor is 2 rather than the // exact count because this row exists precisely so the NEXT app is scanned // without anyone editing the table. diff --git a/ui/scripts/__tests__/check-node-esm-load.test.ts b/ui/scripts/__tests__/check-node-esm-load.test.ts index e8bb5f4306e..f7741fbf9c2 100644 --- a/ui/scripts/__tests__/check-node-esm-load.test.ts +++ b/ui/scripts/__tests__/check-node-esm-load.test.ts @@ -3,6 +3,7 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { UI_PUBLISHED_PACKAGE_FLOOR } from './helpers/ui-package-population.mjs'; import { ESM_MODULE_EDGE, @@ -448,7 +449,10 @@ describe('the load leg evaluates rather than resolves', () => { describe('the gate cannot be green about nothing', () => { it('keeps a floor under both legs', () => { - expect(MIN_PACKAGES).toBeGreaterThanOrEqual(30); + // The package floor is the shared ui published-package floor, not a number of + // its own: re-measuring the population moves it in one place for every reader. + expect(MIN_PACKAGES).toBe(UI_PUBLISHED_PACKAGE_FLOOR); + expect(MIN_PACKAGES).toBeGreaterThan(0); expect(MIN_LOADED).toBeGreaterThan(0); }); diff --git a/ui/scripts/__tests__/helpers/ui-package-population.mjs b/ui/scripts/__tests__/helpers/ui-package-population.mjs new file mode 100644 index 00000000000..a447153499c --- /dev/null +++ b/ui/scripts/__tests__/helpers/ui-package-population.mjs @@ -0,0 +1,62 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * ui-package-population -- the ONE place the size of the ui package population + * is written down, as anti-vacuity floors. + * + * import { UI_PUBLISHED_PACKAGE_FLOOR } from './helpers/ui-package-population.mjs'; + * expect(published.length).toBeGreaterThanOrEqual(UI_PUBLISHED_PACKAGE_FLOOR); + * + * A floor here answers one question: "did the scan find a population at all, + * or did a lost workspace root, a broken glob or a renamed manifest field leave + * it reading nothing?" It is NOT a census. A census pinned at today's measured + * value goes red on every legitimate package deletion, and one deletion then + * edits a floor in every file that spelled the count (objectstack#26444 counted + * five for a single retirement). So each floor sits at about two thirds of the + * value measured below -- the `vite-dts-wiring-ratchet` precedent: measured 22, + * floor 15 -- and is written once, here, for every reader: + * + * - `scripts/check-node-esm-load.mjs` and `scripts/check-published-dist-tooling.mjs` + * (`MIN_PACKAGES`, the gates' own refusal to judge a collapsed scan); + * - `package-files-exist`, `side-effects-declaration-consistency`, + * `check-dist-completeness`, `zero-test-member-exclusion-9106`, + * `check-doc-links` and `quick-reference-commands-4149` under `scripts/__tests__/`. + * + * It is an `.mjs` module, not a `.ts` helper like its neighbours, because the two + * gate scripts above import it at run time under plain `node`. + * + * Re-measuring: a floor moves only when its population has shrunk to near it, + * and then to about two thirds of the new measurement, with the measurement and + * its commit recorded in the table below. ⛔ Never raise a floor to track growth: + * growth is routine and the floor's job is only to refuse an empty scan. + * + * Measured on objectstack `origin/main` `6a7c0fe3` (2026-10-27, right after + * `@object-ui/app-shell` became private): + * + * population measured floor margin + * published packages (release group) 36 24 12 + * workspace members (packages/*, apps/*, examples/*) 43 28 15 + * `sideEffects: false` declarers 11 7 4 + * `sideEffects` array declarers 17 11 6 + * tsc-built packages 11 7 4 + */ + +/** + * Packages that publish to npm: not `private: true`, in the changesets release + * group. Readers whose population is a superset of these (packages declaring + * `files` or `exports`, package directories carrying a README) read this floor + * too: if the published set is there, so is theirs. + */ +export const UI_PUBLISHED_PACKAGE_FLOOR = 24; + +/** Every manifest under the three workspace globs, private ones included. */ +export const UI_WORKSPACE_MEMBER_FLOOR = 28; + +/** Packages declaring `"sideEffects": false`. */ +export const UI_SIDE_EFFECTS_FALSE_FLOOR = 7; + +/** Packages declaring `sideEffects` as an array of paths. */ +export const UI_SIDE_EFFECTS_ARRAY_FLOOR = 11; + +/** Packages whose build runs `tsc` (the `check-dist-completeness` population). */ +export const UI_TSC_BUILT_PACKAGE_FLOOR = 7; diff --git a/ui/scripts/__tests__/package-files-exist.test.ts b/ui/scripts/__tests__/package-files-exist.test.ts index 76ca33bae89..e70e46afd78 100644 --- a/ui/scripts/__tests__/package-files-exist.test.ts +++ b/ui/scripts/__tests__/package-files-exist.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { workspaceGlobs as readWorkspaceGlobs } from '../workspace-root.mjs'; +import { UI_PUBLISHED_PACKAGE_FLOOR } from './helpers/ui-package-population.mjs'; import fs from 'node:fs'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; @@ -229,12 +230,15 @@ const missing = declared.filter((d) => !d.onDisk && !isDeclaredBuildOutput(d)); describe('package.json `files` entries exist on disk (objectui#3663)', () => { it('discovers the workspace (guard cannot pass by finding nothing)', () => { // npm's silence is the whole hazard here, so a guard that inspects an empty - // list would reproduce it exactly: green output, nothing checked. These - // floors sit just under the measured values on main@dae1ac41e (40 packages - // declaring `files`, 159 entries) so that losing a workspace root, a broken - // glob parser or a moved directory is a failure and not a quiet pass. - expect(packagesWithFiles.length).toBeGreaterThanOrEqual(38); - expect(declared.length).toBeGreaterThanOrEqual(150); + // list would reproduce it exactly: green output, nothing checked. Losing a + // workspace root, a broken glob parser or a moved directory must be a + // failure and not a quiet pass. Every published package declares `files`, + // so the shared published-package floor bounds this population too (39 + // measured on objectstack 6a7c0fe3). The entry floor is about two thirds of + // the 153 entries measured there: a floor against an empty scan, not a + // census, so deleting a package leaves both green. + expect(packagesWithFiles.length).toBeGreaterThanOrEqual(UI_PUBLISHED_PACKAGE_FLOOR); + expect(declared.length).toBeGreaterThanOrEqual(100); // The specimen package must be in scope by name: if plugin-tree ever drops // out of the scan, the guard has stopped watching the exact spot that @@ -439,15 +443,12 @@ const KNOWN_LICENSE_TEXT_MISSING: Record = {}; describe('published packages that declare a license ship its text (objectui#3696)', () => { it('discovers the packages that owe license text (guard cannot pass by finding nothing)', () => { - // Same anti-vacuity floor as the guard above, sitting just under the 39 - // measured on main@3e601773e. A predicate that quietly matches nothing — - // a renamed `license` field, a lost workspace root, a `private` default - // flipped — would otherwise report success over an empty set. Lowered by - // one when `@object-ui/sdui-parser` was retired for the root - // `@objectstack/sdui-parser` (objectstack#25712 item 3): 37 measured then. - // Lowered by one again when `@object-ui/app-shell` went private - // (objectstack#25922): 36 measured then. - expect(owingPackages.length).toBeGreaterThanOrEqual(36); + // Same anti-vacuity floor as the guard above: the shared published-package + // floor (36 owing measured on objectstack 6a7c0fe3). A predicate that + // quietly matches nothing — a renamed `license` field, a lost workspace + // root, a `private` default flipped — would otherwise report success over + // an empty set. Retiring or unpublishing a package moves no floor here. + expect(owingPackages.length).toBeGreaterThanOrEqual(UI_PUBLISHED_PACKAGE_FLOOR); // The package objectui#3696 fixed must be in scope BY NAME. If it drops // out of the population, this guard has stopped watching the exact spot @@ -805,13 +806,14 @@ const exportKey = (e: DeclaredExport): string => `${e.pkg.name}${e.subpath === ' describe('every declared `exports` subpath resolves to a file that ships (objectui#4059)', () => { it('discovers the exports surface (guard cannot pass by finding nothing)', () => { - // Same anti-vacuity floor as the two guards above, sitting just under the - // values measured on main@59df371f7 (38 packages declaring `exports`, 140 - // target strings). A broken flattener, a lost workspace root or a renamed + // Same anti-vacuity floors as the two guards above: the shared + // published-package floor (38 packages declaring `exports` measured on + // objectstack 6a7c0fe3) and about two thirds of the 150 target strings + // measured there. A broken flattener, a lost workspace root or a renamed // field would otherwise report success over an empty set — which is the // failure mode this whole file exists to prevent. - expect(packagesWithExports.length).toBeGreaterThanOrEqual(36); - expect(declaredExports.length).toBeGreaterThanOrEqual(130); + expect(packagesWithExports.length).toBeGreaterThanOrEqual(UI_PUBLISHED_PACKAGE_FLOOR); + expect(declaredExports.length).toBeGreaterThanOrEqual(100); // The specimen and its control must be in scope BY NAME and BY SUBPATH. Both // declare the identical `"./style.css": "./dist/index.css"`; the only diff --git a/ui/scripts/__tests__/quick-reference-commands-4149.test.ts b/ui/scripts/__tests__/quick-reference-commands-4149.test.ts index 33dedfb7b85..060d282313c 100644 --- a/ui/scripts/__tests__/quick-reference-commands-4149.test.ts +++ b/ui/scripts/__tests__/quick-reference-commands-4149.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { workspaceGlobs as readWorkspaceGlobs } from '../workspace-root.mjs'; +import { UI_PUBLISHED_PACKAGE_FLOOR } from './helpers/ui-package-population.mjs'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -345,14 +346,16 @@ describe('QUICK_REFERENCE.md — every path it names is in the tree', () => { }); }); -describe('QUICK_REFERENCE.md — the published-package count is derived', () => { +describe('QUICK_REFERENCE.md — the published-package count is not written by hand', () => { /** - * The count kept a number rather than becoming a pointer, because unlike a churning - * enumeration it has an exact anchor (`private` in each manifest) and it tells the - * reader something a pointer cannot: the scale of the repo. What it may not do is be - * written by hand — "39 published packages" counted directories, and - * `packages/vscode-extension` (published to the VS Code marketplace, not to npm) is - * `private: true`. Off by one, in the direction that overstates. + * The row used to state the count ("39 published packages", later 36, then 35), and + * this test derived the true number to hold the prose to it. The first hand-written + * figure counted directories and missed that `packages/vscode-extension` (published + * to the VS Code marketplace, not to npm) is `private: true` — off by one, in the + * direction that overstates. Holding it exact then cost a doc edit on every package + * retirement, for a number the manifests already answer (`private` in each one). So + * the row names the published packages without counting them, and this test keeps it + * that way; the private packages it must still name are checked below. */ const packagesRow = (): string => { const layout = section('## Repository Layout'); @@ -365,30 +368,37 @@ describe('QUICK_REFERENCE.md — the published-package count is derived', () => const published = inPackages.filter((p) => !p.private); const privateOnes = inPackages.filter((p) => p.private); - it('states the number of packages that are actually published', () => { + it('names the published packages without a hand-written count', () => { + // Anti-vacuity: a workspace read that found nothing would make the refusal below + // pass over an empty population. + expect(published.length).toBeGreaterThanOrEqual(UI_PUBLISHED_PACKAGE_FLOOR); + const row = packagesRow(); expect( - packagesRow(), - `${docRel} must state "${published.length} published packages" — that is how many manifests ` + - `under \`packages/\` are not \`private: true\` (${inPackages.length} directories, ` + - `${privateOnes.length} of them private: ${JSON.stringify(privateOnes.map((p) => p.dir))})`, - ).toContain(`${published.length} published`); + row, + `${docRel}'s \`packages/*\` row spells a package count. Drop the number: ${published.length} of ` + + `the ${inPackages.length} manifests under \`packages/\` are not \`private: true\` today, and a ` + + 'written figure goes stale on the next retirement.', + ).not.toMatch(/\b\d+\s+(?:published|packages)\b/i); + expect(row, `${docRel}'s \`packages/*\` row must still say these are the published packages`).toMatch( + /published/i, + ); }); it('accounts for the private packages instead of quietly dropping them', () => { if (privateOnes.length === 0) { - // Nothing to reconcile: the count and the directory listing agree, so the row - // saying only "N published packages" is complete. This branch exists so the - // requirement below retires itself the day the last private package leaves. + // Nothing to reconcile: every directory publishes, so the row saying only "the + // published packages" is complete. This branch exists so the requirement below + // retires itself the day the last private package leaves. return; } const row = packagesRow(); expect( row, - `${docRel}'s \`packages/*\` row states a published count that is smaller than the ` + - `${inPackages.length} directories a reader sees there. It must say why — name the private ` + - `package(s) ${JSON.stringify(privateOnes.map((p) => p.name))} — or the next reader "corrects" ` + - 'the number back to the directory count, which is exactly how objectui#4149 happened.', + `${docRel}'s \`packages/*\` row calls the packages published, but ${privateOnes.length} of the ` + + `${inPackages.length} directories a reader sees there are not. It must say why — name the private ` + + `package(s) ${JSON.stringify(privateOnes.map((p) => p.name))} — or the next reader writes ` + + 'the directory count back in as the published count, which is exactly how objectui#4149 happened.', ).toMatch(/private/i); for (const pkg of privateOnes) { diff --git a/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts b/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts index 02ca9845998..79a339c257b 100644 --- a/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts +++ b/ui/scripts/__tests__/side-effects-declaration-consistency.test.ts @@ -4,6 +4,7 @@ import { workspaceGlobs as readWorkspaceGlobs } from '../workspace-root.mjs'; // The shared registration reader (objectui#4894). Types are INFERRED from the // .mjs source by `tsconfig.scripts.json` (`allowJs`), so no `@ts-expect-error`. import { readComponentRegistrations } from '../component-registrations.mjs'; +import { UI_SIDE_EFFECTS_ARRAY_FLOOR, UI_SIDE_EFFECTS_FALSE_FLOOR } from './helpers/ui-package-population.mjs'; import fs from 'node:fs'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; @@ -1071,16 +1072,15 @@ const falseBarrelProbes: ProbeCase[] = falsePackages.flatMap((pkg) => { describe('`sideEffects` declarations match load-time behaviour (objectui#3943)', () => { it('discovers the declaring packages (guard cannot pass by finding nothing)', () => { // The hazard here is a SILENT one, so a guard that inspected an empty list - // would reproduce it exactly: green output, nothing checked. These floors - // sit at the census re-measured for objectui#13464, when every published - // package came to declare the field: 12 `false` and 17 arrays, the rest - // `true` — so a lost workspace root, a broken glob parser or a renamed - // field is a failure and not a quiet pass. One fewer since - // `@object-ui/sdui-parser` (a `false`) was retired for the root - // `@objectstack/sdui-parser` (objectstack#25712 item 3): 28, 11 of them `false`. - expect(declaringPackages.length).toBeGreaterThanOrEqual(28); - expect(falsePackages.length).toBeGreaterThanOrEqual(11); - expect(arrayPackages.length).toBeGreaterThanOrEqual(17); + // would reproduce it exactly: green output, nothing checked. So a lost + // workspace root, a broken glob parser or a renamed field must be a failure + // and not a quiet pass. The floors are the shared ones, about two thirds of + // the census measured on objectstack 6a7c0fe3 (11 `false`, 17 arrays, 28 + // declaring), so retiring a package moves none of them; the declaring floor + // is their sum, since the declarers are exactly those two groups. + expect(declaringPackages.length).toBeGreaterThanOrEqual(UI_SIDE_EFFECTS_FALSE_FLOOR + UI_SIDE_EFFECTS_ARRAY_FLOOR); + expect(falsePackages.length).toBeGreaterThanOrEqual(UI_SIDE_EFFECTS_FALSE_FLOOR); + expect(arrayPackages.length).toBeGreaterThanOrEqual(UI_SIDE_EFFECTS_ARRAY_FLOOR); // The census by name. `true` packages (`components`, `fields`, four // plugins and the executables) are excluded on purpose: `true` is the conservative claim and can never lose a diff --git a/ui/scripts/__tests__/zero-test-member-exclusion-9106.test.ts b/ui/scripts/__tests__/zero-test-member-exclusion-9106.test.ts index 862c39ba63d..14e4a062ca5 100644 --- a/ui/scripts/__tests__/zero-test-member-exclusion-9106.test.ts +++ b/ui/scripts/__tests__/zero-test-member-exclusion-9106.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { workspaceGlobs } from '../workspace-root.mjs'; +import { UI_WORKSPACE_MEMBER_FLOOR } from './helpers/ui-package-population.mjs'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -200,21 +201,22 @@ describe('objectui#9106 — the derivation itself can fail', () => { }); it('finds the whole workspace, not a slice of it', () => { - // Measured on 1e0e46af9: 46 manifests under the three globbed groups. - // A floor, not an equality — new packages are routine — but a floor low - // enough to be honest and high enough that a half-broken enumeration fails. - expect(members.length).toBeGreaterThanOrEqual(40); + // A floor, not an equality — packages come and go routinely — but high + // enough that a collapsed enumeration fails: the shared workspace-member + // floor, about two thirds of the 43 manifests under the three globbed + // groups measured on objectstack 6a7c0fe3. + expect(members.length).toBeGreaterThanOrEqual(UI_WORKSPACE_MEMBER_FLOOR); expect(members.map((m) => m.dir)).toContain('packages/layout'); expect(members.map((m) => m.dir)).toContain('apps/console'); }); it('can see test files where test files are', () => { // The negative control for `collectTestFiles`: a collector that returned [] - // for everything would put all 46 members in the excluded bucket and this + // for everything would put every member in the excluded bucket and this // file would then be asserting nothing at all. const layout = members.find((m) => m.dir === 'packages/layout'); expect(layout?.testFileCount).toBeGreaterThan(0); - expect(withScriptAndFiles.length).toBeGreaterThanOrEqual(40); + expect(withScriptAndFiles.length).toBeGreaterThanOrEqual(UI_WORKSPACE_MEMBER_FLOOR); }); }); diff --git a/ui/scripts/check-node-esm-load.mjs b/ui/scripts/check-node-esm-load.mjs index 62518cad354..8db6f1f1db6 100644 --- a/ui/scripts/check-node-esm-load.mjs +++ b/ui/scripts/check-node-esm-load.mjs @@ -150,17 +150,20 @@ import ts from 'typescript'; import { SKIP_DIRS, TOOLING_FILE, discoverPackages, moduleSpecifiers } from './check-phantom-dependencies.mjs'; import { isEntrypoint } from './invoked-as.mjs'; +import { UI_PUBLISHED_PACKAGE_FLOOR } from './__tests__/helpers/ui-package-population.mjs'; const scriptDir = dirname(fileURLToPath(import.meta.url)); /** * Below this many published packages the scan is broken, not empty. * - * The `fixed` group holds 40 entries today. A collapse to a handful means the - * release config or the manifest walk stopped answering, and every assertion - * below would pass while reading nothing. + * A collapse to a handful means the release config or the manifest walk stopped + * answering, and every assertion below would pass while reading nothing. The + * number is the shared ui published-package floor, written once with its + * measurement in `__tests__/helpers/ui-package-population.mjs`, so deleting a + * package never edits this file. */ -export const MIN_PACKAGES = 30; +export const MIN_PACKAGES = UI_PUBLISHED_PACKAGE_FLOOR; /** * Below this many SUCCESSFUL imports the load leg proved nothing. diff --git a/ui/scripts/check-published-dist-tooling.mjs b/ui/scripts/check-published-dist-tooling.mjs index 48ea5d546a8..f2d3eb9e236 100644 --- a/ui/scripts/check-published-dist-tooling.mjs +++ b/ui/scripts/check-published-dist-tooling.mjs @@ -135,6 +135,7 @@ import { fileURLToPath } from 'node:url'; import { PACKAGE_ROOTS, TOOLING_FILE, readReleaseGroup } from './check-phantom-dependencies.mjs'; import { isEntrypoint } from './invoked-as.mjs'; +import { UI_PUBLISHED_PACKAGE_FLOOR } from './__tests__/helpers/ui-package-population.mjs'; const scriptDir = dirname(fileURLToPath(import.meta.url)); @@ -151,11 +152,13 @@ export const BUILD_OUTPUT_DIRS = ['dist', 'build', '.next']; /** * Below this many published packages the scan is broken, not empty. * - * The `fixed` group holds 40 entries today, one of them private. A collapse to - * a handful means the release config, the manifest walk or `npm pack` stopped - * answering, and every assertion below would pass while reading nothing. + * A collapse to a handful means the release config, the manifest walk or + * `npm pack` stopped answering, and every assertion below would pass while + * reading nothing. The number is the shared ui published-package floor, written + * once with its measurement in `__tests__/helpers/ui-package-population.mjs`, so + * deleting a package never edits this file. */ -export const MIN_PACKAGES = 30; +export const MIN_PACKAGES = UI_PUBLISHED_PACKAGE_FLOOR; // ── the tooling convention, derived rather than re-spelled ───────────────────