@estiva-app/ui 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/registry/cli.ts", "../../src/registry/siblings.ts", "../../src/registry/skill.ts", "../../src/registry/storymap.ts"],
4
- "sourcesContent": ["/// <reference types=\"node\" />\n/**\n * `estiva-ui` \u2014 the catalogue as a command (UIG-12, UIG-13; docs/GATES.md \u00A723\n * for why it ships in the package rather than as a script pasted into each repo).\n *\n * estiva-ui find <words\u2026> [--here | --also [name=]<folder>\u2026] [--json] [--limit n]\n * estiva-ui build [--out <path>]\n * estiva-ui check\n * estiva-ui skill [--out <folder>]\n *\n * Every command takes `--root <folder>` (the library it reads; default: here)\n * and `--repo <name>` (what an app's entries are called; default: its package name).\n *\n * **In the package** (`@estiva-app/ui` itself): `build` writes the committed\n * `registry.json`, and `check` rebuilds it and fails on a single byte of drift.\n *\n * **In an app**: nothing is committed (Katerina, 18 September 2026 \u2014 the\n * package is public, the apps are private). `check` builds the app's catalogue\n * and fails when a part has no one-line description or cannot be sorted, or\n * when a reusable part breaks the usage-page contract (UIG-19, `./contract`),\n * or when the app's hand-written map of its Storybook no longer matches it\n * (`./storymap`); CI's `gate` job runs it. `build` writes the catalogue to a file only when asked.\n *\n * `find` searches the package's catalogue \u2014 the one shipped inside the installed\n * package, or this repo's own in the package \u2014 plus, in an app, the app's own,\n * built fresh, plus every app beside it (UIG-20, `./siblings`): found, not\n * named, so one command answers the same from any repository, or from the\n * folder that holds them. `--here` searches no neighbour; `--also` names them.\n *\n * `skill` writes the repository's Claude skill loader (UIG-20, `./skill`), or\n * the one for `--out <folder>`; `check` fails when the repository's is missing\n * or differs.\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, dirname, join, relative, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { CONTRACT_KINDS, contractProblems, linkProblems, nameProblems } from './contract'\nimport { findInRegistries, formatFindings } from './find'\nimport { behindBy, findSiblings, workspaceOf } from './siblings'\nimport { loaderPath, loaderProblem, loaderText, repositoryOf, settingsPath, settingsWithSearch } from './skill'\nimport { indexHeadings, storyMapProblems } from './storymap'\nimport { CLASSES, validateRegistry, type Registry } from './schema'\n\n/**\n * The builders, loaded only when they are used.\n *\n * They read TypeScript with TypeScript, and `typescript` is a dev dependency of\n * the app that installs this package \u2014 a real one in Peek, Ship and this repo,\n * and possibly absent elsewhere. A static import made the bundler put the whole\n * builder in the same chunk as `find`, so `npx estiva-ui find \u2026` died with\n * ERR_MODULE_NOT_FOUND before opening the file it needed.\n */\nconst packageBuilder = () => import('./build')\nconst appBuilder = () => import('./app')\nconst gatedScan = () => import('./gated')\n\nconst [command, ...rest] = process.argv.slice(2)\nconst VALUED = ['--limit', '--out', '--file', '--root', '--repo', '--also']\nconst flag = (name: string) => rest.includes(`--${name}`)\nconst value = (name: string) => {\n const at = rest.indexOf(`--${name}`)\n return at === -1 ? undefined : rest[at + 1]\n}\nconst values = (name: string) => rest.flatMap((word, i) => (word === `--${name}` && rest[i + 1] !== undefined ? [rest[i + 1]] : []))\n/** Everything that is not a flag or a flag's value: the words of the question. */\nconst plain = () => {\n const out: string[] = []\n for (let i = 0; i < rest.length; i++) {\n if (rest[i].startsWith('--')) {\n if (VALUED.includes(rest[i])) i += 1\n continue\n }\n out.push(rest[i])\n }\n return out\n}\n\nconst root = resolve(value('root') ?? '.')\nconst manifestAt = (folder: string) => JSON.parse(readFileSync(resolve(folder, 'package.json'), 'utf8')) as { name?: string }\n/** The package itself, or an app that installs it. */\nconst isPackage = existsSync(resolve(root, 'package.json')) && manifestAt(root).name === '@estiva-app/ui'\n\n/** The package's committed catalogue: `--file`, this repo's own in the package, else the copy inside the installed package. */\nfunction packageRegistryPath(): string {\n const named = value('file')\n if (named) return resolve(named)\n if (isPackage) return resolve(root, 'registry.json')\n // dist/registry/cli.js \u2192 the package's own root. Never an app's own folder:\n // a registry.json there is the app's, written by `estiva-ui build`.\n return fileURLToPath(new URL('../../registry.json', import.meta.url))\n}\n\nfunction readPackageRegistry(): Registry {\n const path = packageRegistryPath()\n if (!existsSync(path)) throw new Error(`no registry.json at ${path} \u2014 run \"estiva-ui build\" in the package first`)\n const registry = JSON.parse(readFileSync(path, 'utf8')) as Registry\n const problems = validateRegistry(registry)\n // A malformed catalogue is worse than none: it would answer, wrongly.\n if (problems.length) throw new Error(`${path} does not match the schema:\\n ${problems.join('\\n ')}`)\n return registry\n}\n\n/** The installed package whose skill text a loader reads: this repo in the package, else the app's install. */\nconst packageRoot = () => (isPackage ? root : resolve(root, 'node_modules', '@estiva-app', 'ui'))\n\n/** The Claude skill loader (UIG-20): reports, and says whether it passed. */\nfunction checkLoader(): boolean {\n const problem = loaderProblem(repositoryOf(root), packageRoot())\n if (problem) {\n process.stderr.write(`claude skill: ${problem}\\n`)\n return false\n }\n process.stdout.write('claude skill: the loader reads the package\u2019s text\\n')\n return true\n}\n\nasync function buildApp(folder: string, repo: string | undefined): Promise<Registry> {\n const { buildAppRegistry } = await appBuilder()\n const registry = buildAppRegistry({ root: folder, repo })\n const problems = validateRegistry(registry)\n if (problems.length) throw new Error(`the catalogue of ${registry.builtFrom.repo} does not match its own schema:\\n ${problems.join('\\n ')}`)\n return registry\n}\n\n/**\n * Every story and docs link in a catalogue, against the Storybook beside it\n * (UIG-19). `storybook index` writes the ids Storybook itself would serve \u2014 no\n * server, no browser, seconds \u2014 so a renamed story, or a page that no longer\n * compiles, fails here rather than as a dead link somebody finds later.\n * Reports, and says whether it passed. A folder with no Storybook has nothing\n * to check.\n */\nfunction checkLinks(registry: Registry, folder: string): { ok: boolean; headings?: Set<string>; titles?: Set<string> } {\n const bin = resolve(folder, 'node_modules', '.bin', process.platform === 'win32' ? 'storybook.cmd' : 'storybook')\n if (!existsSync(resolve(folder, '.storybook')) || !existsSync(bin)) {\n process.stdout.write('story links: no Storybook here, nothing to check\\n')\n return { ok: true }\n }\n const out = join(mkdtempSync(join(tmpdir(), 'estiva-ui-index-')), 'index.json')\n // A `.cmd` needs a shell on Windows, and Node wants that as one quoted line\n // rather than an argument list. Both paths are ours: the app's folder and a temp file.\n const run =\n process.platform === 'win32'\n ? spawnSync(`\"${bin}\" index -o \"${out}\" --quiet`, { cwd: folder, encoding: 'utf8', shell: true })\n : spawnSync(bin, ['index', '-o', out, '--quiet'], { cwd: folder, encoding: 'utf8' })\n if (run.status !== 0 || !existsSync(out)) {\n process.stderr.write(`story links: Storybook could not index its stories, so no link can be trusted:\\n${(run.stderr || run.stdout || '').trim()}\\n`)\n return { ok: false }\n }\n const entries = (JSON.parse(readFileSync(out, 'utf8')) as { entries: Record<string, { title?: string }> }).entries\n const ids = new Set(Object.keys(entries))\n const headings = indexHeadings(entries)\n const titles = new Set(Object.values(entries).flatMap((e) => (e.title ? [e.title] : [])))\n const dead = linkProblems(registry, ids)\n const count = registry.entries.reduce((n, entry) => n + (entry.storyId ? 1 : 0) + (entry.docsId ? 1 : 0), 0)\n if (dead.length) {\n process.stderr.write(`story links: ${dead.length} of ${count} lead nowhere:\\n ${dead.join('\\n ')}\\n`)\n return { ok: false, headings, titles }\n }\n process.stdout.write(`story links: all ${count} open a story or docs page Storybook has\\n`)\n return { ok: true, headings, titles }\n}\n\n/**\n * What a part draws only when a prop is passed, where no story passes it\n * (UIG-19, `./gated`). Strict, by Katerina's ruling: a part's own stories show\n * everything it can draw. In an app it reads the reusable parts; in the package,\n * every component (`kinds` is the package's one class, `component`).\n */\nasync function checkGated(registry: Registry, folder: string, kinds?: string[]): Promise<boolean> {\n const { gatedFindings } = await gatedScan()\n const found = gatedFindings(registry, folder, { ...(kinds ? { kinds } : {}), pictured: 'own' })\n if (found.length) {\n const lines = found.map((f) => `${f.part} (${f.file}) draws something only when given \\`${f.prop}\\`, and no story that draws it passes \\`${f.prop}\\`: pass it in a story`)\n process.stderr.write(`stories: ${found.length === 1 ? 'one thing a part draws is' : `${found.length} things parts draw are`} never shown:\\n ${lines.join('\\n ')}\\n`)\n return false\n }\n process.stdout.write('stories: everything a part draws behind a prop is shown by a story\\n')\n return true\n}\n\n/**\n * The repository's hand-written map of its Storybook \u2014 the heading order and the\n * Introduction \u2014 against the Storybook (its own index, when there is one) and,\n * in an app, the catalogue (`./storymap`). Reports, and says whether it passed.\n */\nfunction checkStoryMap(folder: string, registry?: Registry, headings?: Set<string>): boolean {\n const problems = storyMapProblems(folder, { ...(registry ? { registry } : {}), ...(headings ? { real: headings } : {}) })\n if (problems.length) {\n process.stderr.write(`storybook map: ${problems.length === 1 ? 'one line no longer matches' : `${problems.length} lines no longer match`}:\\n ${problems.join('\\n ')}\\n`)\n return false\n }\n process.stdout.write('storybook map: the heading order and the Introduction match the Storybook and the catalogue\\n')\n return true\n}\n\n/** One line per kind, in a fixed order, so two runs read the same. */\nfunction summary(registry: Registry): string {\n const count = (cls: string) => registry.entries.filter((entry) => entry.app?.class === cls).length\n const kinds = CLASSES.map((cls) => `${count(cls)} ${cls}`).join(', ')\n const files = registry.builtFrom.files ?? 0\n return `${registry.builtFrom.repo}: ${registry.entries.length} parts in ${files - registry.filesWithoutParts.length} files (${registry.filesWithoutParts.length} more hold none) \u2014 ${kinds}. Every part is described and sorted.`\n}\n\nasync function main(): Promise<number> {\n switch (command) {\n case 'find': {\n const query = plain().join(' ')\n if (!query.trim()) {\n process.stderr.write('estiva-ui find <words\u2026> \u2014 what the thing you need does, in words\\n')\n return 1\n }\n const registries: Registry[] = [readPackageRegistry()]\n // An app that cannot be read is said out loud, and the rest still answer.\n const add = async (folder: string, repo: string | undefined) => {\n const behind = behindBy(folder)\n if (behind) process.stderr.write(`note: ${repo ?? basename(folder)} (${folder}) is ${behind} commit${behind === 1 ? '' : 's'} behind main as last fetched \u2014 its answers may name parts that are gone; pull it\\n`)\n try {\n registries.push(await buildApp(folder, repo))\n } catch (error) {\n const [first, ...more] = String(error instanceof Error ? error.message : error).split('\\n')\n process.stderr.write(`not searched: ${first}${more.length ? `\\n${more.slice(0, 3).join('\\n')}${more.length > 3 ? `\\n \u2026and ${more.length - 3} more \u2014 run \"estiva-ui check\" there` : ''}` : ''}\\n`)\n }\n }\n // The folder it runs in is searched when it is an app; the folder that\n // holds the repositories is not one, and only its neighbours answer.\n const here = !isPackage && existsSync(resolve(root, 'package.json')) ? (manifestAt(root).name ?? basename(root)) : null\n if (here) await add(root, value('repo'))\n if (!values('also').length && !flag('here')) {\n for (const sibling of findSiblings(workspaceOf(root), { skip: here ? [here] : [] })) await add(sibling.folder, sibling.name)\n }\n for (const also of values('also')) {\n const [name, folder] = also.includes('=') ? [also.slice(0, also.indexOf('=')), also.slice(also.indexOf('=') + 1)] : [undefined, also]\n const at = resolve(root, folder)\n if (!existsSync(resolve(at, 'package.json'))) {\n process.stderr.write(`not searched: ${name ?? basename(at)} \u2014 nothing at ${at}\\n`)\n continue\n }\n await add(at, name)\n }\n const limit = Number(value('limit') ?? 5)\n const findings = findInRegistries(registries, query, { limit: Number.isFinite(limit) && limit > 0 ? limit : 5 })\n if (flag('json')) {\n process.stdout.write(`${JSON.stringify(findings.map((finding) => finding.entry), null, 2)}\\n`)\n return 0\n }\n process.stdout.write(`${formatFindings(registries, findings, query)}\\n`)\n // Nothing found is not a failure \u2014 it is the answer that sends a session\n // to ask rather than to invent.\n return 0\n }\n\n case 'build': {\n const out = resolve(root, value('out') ?? 'registry.json')\n if (isPackage) {\n const { buildRegistry, serializeRegistry } = await packageBuilder()\n const registry = buildRegistry({ root })\n const problems = validateRegistry(registry)\n if (problems.length) {\n process.stderr.write(`the registry this build produced does not match its own schema:\\n ${problems.join('\\n ')}\\n`)\n return 1\n }\n writeFileSync(out, serializeRegistry(registry), 'utf8')\n process.stdout.write(`${out}: ${registry.entries.length} entries from ${registry.builtFrom.exports} exports\\n`)\n return 0\n }\n const { serializeRegistry } = await packageBuilder()\n const registry = await buildApp(root, value('repo'))\n writeFileSync(out, serializeRegistry(registry), 'utf8')\n process.stdout.write(`${out}: ${summary(registry)}\\n`)\n return 0\n }\n\n case 'check': {\n if (!isPackage) {\n const registry = await buildApp(root, value('repo'))\n process.stdout.write(`${summary(registry)}\\n`)\n // The usage-page contract and the story links (UIG-19): the one copy, run by every app's `gate` job.\n // A When not may name a part of the package or of this app (B8).\n const pkg = readPackageRegistry()\n const known = new Set([...pkg.entries, ...registry.entries].map((e) => e.name))\n const broken = contractProblems(registry, root, known)\n if (broken.length) {\n process.stderr.write(`${broken.length === 1 ? 'a part breaks' : `${broken.length} parts break`} the usage-page contract:\\n ${broken.join('\\n ')}\\n`)\n } else {\n const owed = registry.entries.filter((entry) => (CONTRACT_KINDS as readonly string[]).includes(entry.app?.class ?? '')).length\n process.stdout.write(`usage pages: all ${owed} reusable parts keep the contract and are drawn somewhere\\n`)\n }\n const linked = checkLinks(registry, root)\n const shown = await checkGated(registry, root)\n const loaded = checkLoader()\n const mapped = checkStoryMap(root, registry, linked.headings)\n // What the pages name must still be there (R14): parts under When and When not,\n // and the stories a Seen in line sends a reader to, here or in the package.\n const stale = nameProblems(registry, root, {\n known,\n ...(linked.titles ? { titles: linked.titles } : {}),\n packageIds: pkg.entries.flatMap((e) => [e.storyId, e.docsId].filter((id): id is string => Boolean(id))),\n })\n if (stale.length) process.stderr.write(`usage pages: ${stale.length === 1 ? 'one name leads' : `${stale.length} names lead`} nowhere:\\n ${stale.join('\\n ')}\\n`)\n else process.stdout.write('usage pages: every part and story they name is there\\n')\n return broken.length || stale.length || !linked.ok || !shown || !loaded || !mapped ? 1 : 0\n }\n const { buildRegistry, serializeRegistry } = await packageBuilder()\n const path = packageRegistryPath()\n const built = buildRegistry({ root })\n const problems = validateRegistry(built)\n if (problems.length) {\n process.stderr.write(`the registry does not match its own schema:\\n ${problems.join('\\n ')}\\n`)\n return 1\n }\n if (!existsSync(path)) {\n process.stderr.write(`${path} is missing \u2014 run \"npm run registry\"\\n`)\n return 1\n }\n // Byte for byte. The point of the check is that the committed file cannot\n // drift from the code; \"near enough\" is drift.\n if (readFileSync(path, 'utf8').replace(/\\r\\n/g, '\\n') !== serializeRegistry(built)) {\n process.stderr.write(`${path} is not what the code produces \u2014 run \"npm run registry\" and commit the result\\n`)\n return 1\n }\n process.stdout.write(`${path}: current, ${built.entries.length} entries from ${built.builtFrom.exports} exports\\n`)\n const linked = checkLinks(built, root)\n const shown = await checkGated(built, root, ['component'])\n const loaded = checkLoader()\n // The package keeps a heading order too (R16): it left out Layout, and nothing saw.\n const mapped = checkStoryMap(root, undefined, linked.headings)\n return linked.ok && shown && loaded && mapped ? 0 : 1\n }\n\n case 'skill': {\n const out = value('out')\n const folder = out ? resolve(out) : repositoryOf(root)\n // The loader's path starts where the session does and cannot climb out:\n // Claude Code refuses a `..` in it.\n if (relative(folder, packageRoot()).startsWith('..')) {\n process.stderr.write(`${folder} does not hold ${packageRoot()}: a loader there could not reach the skill's text\\n`)\n return 1\n }\n const path = loaderPath(folder)\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, loaderText(folder, packageRoot()), 'utf8')\n process.stdout.write(`${path}: the Claude skill, read from ${packageRoot()}\\n`)\n const settings = settingsWithSearch(folder)\n if (settings) {\n writeFileSync(settingsPath(folder), settings, 'utf8')\n process.stdout.write(`${settingsPath(folder)}: the search allowed without a prompt\\n`)\n }\n return 0\n }\n\n default:\n process.stderr.write('estiva-ui <find | build | check | skill>\\n')\n return 1\n }\n}\n\ntry {\n process.exitCode = await main()\n} catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n}\n", "/// <reference types=\"node\" />\n/**\n * The apps beside this one, found rather than named (UIG-20).\n *\n * `estiva-ui find` with nothing else typed searches the package, the app it is\n * run in, and every app that sits beside it. Katerina's condition for having no\n * shared Storybook is that one search, run unasked from any repository, covers\n * every catalogue; a list of names written into each repository's script would\n * be the drifting copy the gates exist to remove, and would miss the next app.\n *\n * An app is a folder, or one folder inside it, whose `package.json` depends on\n * `@estiva-app/ui` and which has a `src` to read. A service that does not\n * install the package is not an app of this catalogue until it does.\n *\n * Each app is searched once. A second checkout of the same app (a worktree, or\n * somebody's copy) carries the same package name; the app the search runs in\n * wins, then the one whose top folder has the shortest name.\n */\nimport { execFileSync } from 'node:child_process'\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'\nimport { basename, dirname, join, resolve } from 'node:path'\n\nexport const PACKAGE_NAME = '@estiva-app/ui'\n\nexport interface Sibling {\n /** What its entries are called: its top folder's name. */\n name: string\n /** The folder the catalogue is built from. */\n folder: string\n /** Its `package.json` name, which says two checkouts are one app. */\n packageName: string\n}\n\nfunction manifest(folder: string): { name?: string; dependencies?: Record<string, string>; devDependencies?: Record<string, string> } | null {\n const file = join(folder, 'package.json')\n if (!existsSync(file)) return null\n try {\n return JSON.parse(readFileSync(file, 'utf8'))\n } catch {\n return null\n }\n}\n\n/** The name of the app in `folder`, or null when the folder is not one. */\nexport function appName(folder: string): string | null {\n const m = manifest(folder)\n if (!m || m.name === PACKAGE_NAME) return null\n const uses = Boolean(m.dependencies?.[PACKAGE_NAME] ?? m.devDependencies?.[PACKAGE_NAME])\n return uses && existsSync(join(folder, 'src')) ? (m.name ?? basename(folder)) : null\n}\n\nconst isDir = (path: string) => {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n\n/**\n * The folder that holds the repositories: the parent of the repository `from`\n * is in, or `from` itself when it is in none (a folder that only holds them).\n */\nexport function workspaceOf(from: string): string {\n let at = resolve(from)\n for (;;) {\n if (existsSync(join(at, '.git'))) return dirname(at)\n const up = dirname(at)\n if (up === at) return resolve(from)\n at = up\n }\n}\n\n/** Every app in `workspace`, one per package name, except those named in `skip`. */\nexport function findSiblings(workspace: string, { skip = [] }: { skip?: string[] } = {}): Sibling[] {\n let tops: string[]\n try {\n tops = readdirSync(workspace).filter((name) => !name.startsWith('.') && name !== 'node_modules' && isDir(join(workspace, name)))\n } catch {\n return []\n }\n const found: Sibling[] = []\n for (const top of tops) {\n const folder = join(workspace, top)\n const inside = readdirSync(folder).filter((name) => !name.startsWith('.') && name !== 'node_modules' && isDir(join(folder, name)))\n for (const candidate of [folder, ...inside.map((name) => join(folder, name))]) {\n const packageName = appName(candidate)\n if (packageName) found.push({ name: top, folder: candidate, packageName })\n }\n }\n const byPackage = new Map<string, Sibling>()\n for (const sibling of found.sort((a, b) => a.name.length - b.name.length || a.name.localeCompare(b.name))) {\n if (skip.includes(sibling.packageName) || byPackage.has(sibling.packageName)) continue\n byPackage.set(sibling.packageName, sibling)\n }\n return [...byPackage.values()].sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/**\n * How many commits the checkout that holds `folder` is behind its remote's main,\n * as last fetched: nothing is fetched, so a search stays quick. Null when it is\n * not a git checkout or has no remote main. A search that reads a stale copy\n * answers with parts that are gone (the re-review after the audit before UIG-26:\n * \"files panel\" found Peek's deleted FilesPanel in an old checkout), so `find`\n * says so beside its answer.\n */\nexport function behindBy(folder: string): number | null {\n const git = (...a: string[]) => {\n try {\n return execFileSync('git', ['-C', folder, ...a], { stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 }).toString().trim()\n } catch {\n return null\n }\n }\n const main = git('symbolic-ref', '--short', 'refs/remotes/origin/HEAD') ?? (git('rev-parse', '--verify', '--quiet', 'origin/main') ? 'origin/main' : null)\n if (!main) return null\n const count = Number(git('rev-list', '--count', `HEAD..${main}`))\n return Number.isFinite(count) ? count : null\n}\n", "/// <reference types=\"node\" />\n/**\n * The Claude skill (UIG-20): search the catalogue before building anything.\n *\n * Its text lives once, in this package, as `skill/estiva-ui.md` (Katerina,\n * 17 September 2026: the same gate text pasted into two repositories is a\n * defect). Claude Code only loads a skill from a `SKILL.md` under a folder's\n * `.claude/skills/`, and no plugin source can point into `node_modules`, so each\n * repository commits a loader: the trigger below, and one line that reads the\n * installed package's text into the skill when it opens (`` !`\u2026` `` runs before\n * Claude sees the skill). `estiva-ui skill` writes it; `estiva-ui check` fails\n * when a repository's loader is missing or is not what this writes.\n *\n * Measured with fresh sessions of Claude Code 2.1.215 (UIG-20), three things\n * shape that line and the settings beside it:\n *\n * - An injected command runs without a prompt only when Claude Code can read it\n * statically as read-only: a plain `cat` of a plain path. A path with `..`\n * (\"may follow a symlink outside the working directory\"), a `$(\u2026)`, or a\n * `node -e` are all refused, and a refused command stops the skill opening.\n * So the path starts at `${CLAUDE_PROJECT_DIR}`, the folder the session\n * started in, as the editor gate's hook does: a session started above or\n * below the repository gets neither.\n * - An `allowed-tools` line makes opening the skill itself ask for permission\n * (\"Execute skill\"). So the search is allowed in the repository's committed\n * `.claude/settings.json` instead, and the skill carries no `allowed-tools`.\n * - Claude reads a skill's description before it opens the skill, so the\n * trigger is written into the loader; it is the one part that cannot be\n * pulled in later.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join, relative, resolve, sep } from 'node:path'\n\nexport const SKILL_NAME = 'estiva-ui'\n\n/** When Claude opens the skill by itself. Kept under Claude Code's 1,536 characters. */\nexport const SKILL_DESCRIPTION =\n 'Use before building, adding or changing anything on screen in an app that uses @estiva-app/ui, or in @estiva-app/ui itself: ' +\n 'a screen, panel, list, row, header, dialog, menu, empty state, a new component, or how something looks. ' +\n 'Also when someone asks for a component in plain words (\"I need a\u2026\", \"add a\u2026\", \"is there a\u2026\"). ' +\n \"It searches every catalogue at once, the package's and each app's Storybook, for a part that already does the job, \" +\n 'and lists the mistakes this codebase keeps making.'\n\n/** The search, allowed without a prompt in the folder's committed `.claude/settings.json`. */\nexport const SEARCH_RULES = ['Bash(npm run ui:find *)', 'Bash(npm --prefix * run ui:find *)', 'PowerShell(npm run ui:find *)', 'PowerShell(npm --prefix * run ui:find *)']\n\n/** The skill's text, inside a package folder. */\nexport const skillTextPath = (packageRoot: string) => join(packageRoot, 'skill', `${SKILL_NAME}.md`)\n\n/** Where a folder keeps its loader. */\nexport const loaderPath = (folder: string) => join(folder, '.claude', 'skills', SKILL_NAME, 'SKILL.md')\n\n/** Where a folder keeps the settings that allow the search. */\nexport const settingsPath = (folder: string) => join(folder, '.claude', 'settings.json')\n\n/** The loader for `folder`, reading the text from `packageRoot`, from where a session in `folder` starts. */\nexport function loaderText(folder: string, packageRoot: string): string {\n const fromTop = relative(folder, skillTextPath(packageRoot)).split(sep).join('/')\n return [\n '---',\n `name: ${SKILL_NAME}`,\n `description: ${JSON.stringify(SKILL_DESCRIPTION)}`,\n '---',\n '',\n '<!-- Written by `estiva-ui skill` from @estiva-app/ui; `estiva-ui check` fails when it differs. The text is the installed package\u2019s, read in below. -->',\n '',\n `!\\`cat \"\\${CLAUDE_PROJECT_DIR}/${fromTop}\"\\``,\n '',\n ].join('\\n')\n}\n\ntype Settings = { permissions?: { allow?: string[] } & Record<string, unknown> } & Record<string, unknown>\n\nconst readSettings = (folder: string): Settings | null => {\n const path = settingsPath(folder)\n if (!existsSync(path)) return {}\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as Settings\n } catch {\n return null\n }\n}\n\n/** `folder`'s settings with the search allowed, everything else as it was; null when they cannot be read. */\nexport function settingsWithSearch(folder: string): string | null {\n const settings = readSettings(folder)\n if (!settings) return null\n const allow = settings.permissions?.allow ?? []\n const missing = SEARCH_RULES.filter((rule) => !allow.includes(rule))\n if (!missing.length && existsSync(settingsPath(folder))) return null\n settings.permissions = { ...settings.permissions, allow: [...allow, ...missing] }\n return `${JSON.stringify(settings, null, 2)}\\n`\n}\n\n/** The repository `from` is in: the nearest folder with `.git`, or `from` when there is none. */\nexport function repositoryOf(from: string): string {\n let at = resolve(from)\n for (;;) {\n if (existsSync(join(at, '.git'))) return at\n const up = dirname(at)\n if (up === at) return resolve(from)\n at = up\n }\n}\n\n/** What is wrong with `folder`'s loader, or null when it is what `loaderText` writes. */\nexport function loaderProblem(folder: string, packageRoot: string): string | null {\n const path = loaderPath(folder)\n const shown = relative(process.cwd(), path).split(sep).join('/') || path\n if (!existsSync(path)) return `${shown} is missing: run \"estiva-ui skill\" and commit it`\n if (readFileSync(path, 'utf8').replace(/\\r\\n/g, '\\n') !== loaderText(folder, packageRoot)) return `${shown} is not what the package writes: run \"estiva-ui skill\" and commit it`\n if (!existsSync(skillTextPath(packageRoot))) return `${shown} reads ${skillTextPath(packageRoot)}, which is not there`\n const allow = readSettings(folder)?.permissions?.allow ?? []\n const missing = SEARCH_RULES.filter((rule) => !allow.includes(rule))\n if (missing.length) return `.claude/settings.json does not allow the search (${missing.join(', ')}): run \"estiva-ui skill\" and commit it`\n return null\n}\n", "/// <reference types=\"node\" />\n/**\n * The hand-written map of a Storybook, held against the Storybook it describes\n * (moved here from Peek's own status checks, audit A3; widened by the re-review\n * after it, R16).\n *\n * A repository may keep two maps of its sidebar: the order of its top-level\n * headings in `.storybook/preview.tsx` \u2014 a `const HEADINGS = [...]` list read by\n * a `storySort` function (Peek), or `storySort: { order: [...] }` (Ship, this\n * package) \u2014 and the Introduction page, which names each heading, in a table row\n * (`| **Folders** | \u2026 |`, Peek) or a bullet (`- **Primitives \u00B7 Inputs** \u2014 \u2026`,\n * Ship), and in an app states how many parts are worth reusing. Both are written\n * by hand and both describe something that moves, so both rot: Peek's named a\n * Huddles heading for a day after its stories were deleted and said 49 reusable\n * parts when the catalogue built 41; Ship's named a Feedback heading it does not\n * have and left out four it does.\n *\n * The real headings are read from Storybook's own index when the command has\n * one, so a title written any way at all counts; otherwise from the story files.\n * Every check runs only when the repository states the thing it checks.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Registry } from './schema'\n\nconst PREVIEW = '.storybook/preview.tsx'\nconst INTRODUCTIONS = ['src/stories/Introduction.mdx', 'stories/Introduction.mdx']\n/** The classes an app's Introduction counts as \"worth reusing\". */\nconst WORTH_REUSING = ['reusable', 'promote-candidate']\n\nfunction storyFiles(root: string): string[] {\n const out: string[] = []\n const walk = (dir: string) => {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n const path = join(dir, entry.name)\n if (entry.isDirectory()) walk(path)\n else if (entry.name.endsWith('.stories.tsx') || entry.name.endsWith('.mdx')) out.push(path)\n }\n }\n walk(join(root, 'src'))\n walk(join(root, 'stories'))\n return out\n}\n\n/** Every top-level heading the sidebar has, read from the story files: the first segment of each title. */\nexport function sidebarHeadings(root: string): Set<string> {\n const real = new Set<string>()\n for (const file of storyFiles(root)) {\n const text = readFileSync(file, 'utf8')\n const start = text.indexOf('const meta')\n const meta = start === -1 ? '' : text.slice(start, text.indexOf('satisfies Meta', start) + 1 || undefined)\n const title = meta.match(/\\btitle: *['\"]([^'\"]+)['\"]/) ?? text.match(/<Meta\\s+title=['\"]([^'\"]+)['\"]/)\n if (title) real.add(title[1].split('/')[0].trim())\n }\n return real\n}\n\n/** The top-level headings of Storybook's own index (`storybook index`): the first segment of every entry's title. */\nexport function indexHeadings(entries: Record<string, { title?: string }>): Set<string> {\n return new Set(Object.values(entries).flatMap((e) => (e.title ? [e.title.split('/')[0].trim()] : [])))\n}\n\n/** The strings at the top level of the array that starts at `open` (the index of its `[`), skipping nested arrays. */\nfunction topLevelStrings(text: string, open: number): string[] {\n const out: string[] = []\n let depth = 0\n for (let i = open; i < text.length; i++) {\n const c = text[i]\n if (c === '/' && text[i + 1] === '*') {\n const end = text.indexOf('*/', i + 2)\n if (end < 0) break\n i = end + 1\n } else if (c === '/' && text[i + 1] === '/') i = text.indexOf('\\n', i)\n else if (c === '[') depth++\n else if (c === ']') {\n if (--depth === 0) return out\n } else if (c === \"'\" || c === '\"') {\n const end = text.indexOf(c, i + 1)\n if (depth === 1) out.push(text.slice(i + 1, end))\n i = end\n }\n if (i < 0) break\n }\n return out\n}\n\n/** The headings the preview orders \u2014 a `HEADINGS` list or `storySort: { order: [...] }` \u2014 or null when it keeps no order. */\nexport function orderedHeadings(previewText: string): string[] | null {\n const list = /const HEADINGS = \\[/.exec(previewText) ?? /\\border:\\s*\\[/.exec(previewText)\n if (!list) return null\n return topLevelStrings(previewText, list.index + list[0].length - 1)\n}\n\n/**\n * The headings the Introduction names: a table row that opens with a bold name,\n * or a bullet that opens with bold names joined by \" \u00B7 \".\n */\nexport function introductionHeadings(introText: string): Set<string> {\n const rows = [...introText.matchAll(/^\\|\\s*\\*\\*([^*]+)\\*\\*\\s*\\|/gm)].map((m) => m[1])\n const bullets = [...introText.matchAll(/^[-*]\\s+\\*\\*([^*]+)\\*\\*\\s*[\u2014\u2013-]/gm)].flatMap((m) => m[1].split('\u00B7'))\n return new Set([...rows, ...bullets].map((h) => h.trim()).filter(Boolean))\n}\n\n/** The number the Introduction states, or null when it states none. */\nexport function introductionCount(introText: string): number | null {\n const said = introText.match(/\\*\\*(\\d+) parts worth reusing\\*\\*/)\n return said ? Number(said[1]) : null\n}\n\n/**\n * What is wrong with the repository's hand-written map of its Storybook; empty\n * when nothing is. `real` is the sidebar's top-level headings from Storybook's\n * index, when the caller has it; `registry` is needed only for an app's count.\n */\nexport function storyMapProblems(root: string, { registry, real = sidebarHeadings(root) }: { registry?: Registry; real?: Set<string> } = {}): string[] {\n const problems: string[] = []\n const previewPath = join(root, PREVIEW)\n if (existsSync(previewPath)) {\n const ordered = orderedHeadings(readFileSync(previewPath, 'utf8'))\n if (ordered) {\n const unordered = [...real].filter((r) => !ordered.includes(r))\n const empty = ordered.filter((o) => !real.has(o))\n if (unordered.length) problems.push(`${PREVIEW}: not in the heading order, so they fall to the bottom of the sidebar: ${unordered.join(', ')}`)\n if (empty.length) problems.push(`${PREVIEW}: in the heading order but no story uses them: ${empty.join(', ')}`)\n }\n }\n const intro = INTRODUCTIONS.find((p) => existsSync(join(root, p)))\n if (intro) {\n const text = readFileSync(join(root, intro), 'utf8')\n const named = introductionHeadings(text)\n if (named.size) {\n const missing = [...real].filter((r) => !named.has(r))\n const stale = [...named].filter((n) => !real.has(n))\n if (missing.length) problems.push(`${intro}: the sidebar has, the page does not name: ${missing.join(', ')}`)\n if (stale.length) problems.push(`${intro}: the page names, the sidebar does not have: ${stale.join(', ')}`)\n }\n const said = introductionCount(text)\n if (said !== null && registry) {\n const reusable = registry.entries.filter((entry) => WORTH_REUSING.includes(entry.app?.class ?? '')).length\n if (said !== reusable) problems.push(`${intro}: says ${said} parts worth reusing; the catalogue builds ${reusable}`)\n }\n }\n return problems\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/// <reference types=\"node\" />\n/**\n * `estiva-ui` \u2014 the catalogue as a command (UIG-12, UIG-13; docs/GATES.md \u00A723\n * for why it ships in the package rather than as a script pasted into each repo).\n *\n * estiva-ui find <words\u2026> [--here | --also [name=]<folder>\u2026] [--json] [--limit n]\n * estiva-ui build [--out <path>]\n * estiva-ui check\n * estiva-ui skill [--out <folder>]\n *\n * Every command takes `--root <folder>` (the library it reads; default: here)\n * and `--repo <name>` (what an app's entries are called; default: its package name).\n *\n * **In the package** (`@estiva-app/ui` itself): `build` writes the committed\n * `registry.json`, and `check` rebuilds it and fails on a single byte of drift.\n *\n * **In an app**: nothing is committed (Katerina, 18 September 2026 \u2014 the\n * package is public, the apps are private). `check` builds the app's catalogue\n * and fails when a part has no one-line description or cannot be sorted, or\n * when a reusable part breaks the usage-page contract (UIG-19, `./contract`),\n * or when the app's hand-written map of its Storybook no longer matches it\n * (`./storymap`); CI's `gate` job runs it. `build` writes the catalogue to a file only when asked.\n *\n * `find` searches the package's catalogue \u2014 the one shipped inside the installed\n * package, or this repo's own in the package \u2014 plus, in an app, the app's own,\n * built fresh, plus every app beside it (UIG-20, `./siblings`): found, not\n * named, so one command answers the same from any repository, or from the\n * folder that holds them. `--here` searches no neighbour; `--also` names them.\n *\n * `skill` writes the repository's Claude skill loader (UIG-20, `./skill`), or\n * the one for `--out <folder>`; `check` fails when the repository's is missing\n * or differs.\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, dirname, join, relative, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { CONTRACT_KINDS, contractProblems, linkProblems, nameableParts, nameProblems } from './contract'\nimport { findInRegistries, formatFindings } from './find'\nimport { behindBy, findSiblings, workspaceOf } from './siblings'\nimport { loaderPath, loaderProblem, loaderText, repositoryOf, settingsPath, settingsWithSearch } from './skill'\nimport { indexHeadings, storyMapProblems } from './storymap'\nimport { CLASSES, validateRegistry, type Registry } from './schema'\n\n/**\n * The builders, loaded only when they are used.\n *\n * They read TypeScript with TypeScript, and `typescript` is a dev dependency of\n * the app that installs this package \u2014 a real one in Peek, Ship and this repo,\n * and possibly absent elsewhere. A static import made the bundler put the whole\n * builder in the same chunk as `find`, so `npx estiva-ui find \u2026` died with\n * ERR_MODULE_NOT_FOUND before opening the file it needed.\n */\nconst packageBuilder = () => import('./build')\nconst appBuilder = () => import('./app')\nconst gatedScan = () => import('./gated')\n\nconst [command, ...rest] = process.argv.slice(2)\nconst VALUED = ['--limit', '--out', '--file', '--root', '--repo', '--also']\nconst flag = (name: string) => rest.includes(`--${name}`)\nconst value = (name: string) => {\n const at = rest.indexOf(`--${name}`)\n return at === -1 ? undefined : rest[at + 1]\n}\nconst values = (name: string) => rest.flatMap((word, i) => (word === `--${name}` && rest[i + 1] !== undefined ? [rest[i + 1]] : []))\n/** Everything that is not a flag or a flag's value: the words of the question. */\nconst plain = () => {\n const out: string[] = []\n for (let i = 0; i < rest.length; i++) {\n if (rest[i].startsWith('--')) {\n if (VALUED.includes(rest[i])) i += 1\n continue\n }\n out.push(rest[i])\n }\n return out\n}\n\nconst root = resolve(value('root') ?? '.')\nconst manifestAt = (folder: string) => JSON.parse(readFileSync(resolve(folder, 'package.json'), 'utf8')) as { name?: string }\n/** The package itself, or an app that installs it. */\nconst isPackage = existsSync(resolve(root, 'package.json')) && manifestAt(root).name === '@estiva-app/ui'\n\n/** The package's committed catalogue: `--file`, this repo's own in the package, else the copy inside the installed package. */\nfunction packageRegistryPath(): string {\n const named = value('file')\n if (named) return resolve(named)\n if (isPackage) return resolve(root, 'registry.json')\n // dist/registry/cli.js \u2192 the package's own root. Never an app's own folder:\n // a registry.json there is the app's, written by `estiva-ui build`.\n return fileURLToPath(new URL('../../registry.json', import.meta.url))\n}\n\nfunction readPackageRegistry(): Registry {\n const path = packageRegistryPath()\n if (!existsSync(path)) throw new Error(`no registry.json at ${path} \u2014 run \"estiva-ui build\" in the package first`)\n const registry = JSON.parse(readFileSync(path, 'utf8')) as Registry\n const problems = validateRegistry(registry)\n // A malformed catalogue is worse than none: it would answer, wrongly.\n if (problems.length) throw new Error(`${path} does not match the schema:\\n ${problems.join('\\n ')}`)\n return registry\n}\n\n/** The installed package whose skill text a loader reads: this repo in the package, else the app's install. */\nconst packageRoot = () => (isPackage ? root : resolve(root, 'node_modules', '@estiva-app', 'ui'))\n\n/** The Claude skill loader (UIG-20): reports, and says whether it passed. */\nfunction checkLoader(): boolean {\n const problem = loaderProblem(repositoryOf(root), packageRoot())\n if (problem) {\n process.stderr.write(`claude skill: ${problem}\\n`)\n return false\n }\n process.stdout.write('claude skill: the loader reads the package\u2019s text\\n')\n return true\n}\n\nasync function buildApp(folder: string, repo: string | undefined): Promise<Registry> {\n const { buildAppRegistry } = await appBuilder()\n const registry = buildAppRegistry({ root: folder, repo })\n const problems = validateRegistry(registry)\n if (problems.length) throw new Error(`the catalogue of ${registry.builtFrom.repo} does not match its own schema:\\n ${problems.join('\\n ')}`)\n return registry\n}\n\n/**\n * Every story and docs link in a catalogue, against the Storybook beside it\n * (UIG-19). `storybook index` writes the ids Storybook itself would serve \u2014 no\n * server, no browser, seconds \u2014 so a renamed story, or a page that no longer\n * compiles, fails here rather than as a dead link somebody finds later.\n * Reports, and says whether it passed. A folder with no Storybook has nothing\n * to check.\n */\nfunction checkLinks(registry: Registry, folder: string): { ok: boolean; headings?: Set<string>; titles?: Set<string> } {\n const bin = resolve(folder, 'node_modules', '.bin', process.platform === 'win32' ? 'storybook.cmd' : 'storybook')\n if (!existsSync(resolve(folder, '.storybook')) || !existsSync(bin)) {\n process.stdout.write('story links: no Storybook here, nothing to check\\n')\n return { ok: true }\n }\n const out = join(mkdtempSync(join(tmpdir(), 'estiva-ui-index-')), 'index.json')\n // A `.cmd` needs a shell on Windows, and Node wants that as one quoted line\n // rather than an argument list. Both paths are ours: the app's folder and a temp file.\n const run =\n process.platform === 'win32'\n ? spawnSync(`\"${bin}\" index -o \"${out}\" --quiet`, { cwd: folder, encoding: 'utf8', shell: true })\n : spawnSync(bin, ['index', '-o', out, '--quiet'], { cwd: folder, encoding: 'utf8' })\n if (run.status !== 0 || !existsSync(out)) {\n process.stderr.write(`story links: Storybook could not index its stories, so no link can be trusted:\\n${(run.stderr || run.stdout || '').trim()}\\n`)\n return { ok: false }\n }\n const entries = (JSON.parse(readFileSync(out, 'utf8')) as { entries: Record<string, { title?: string }> }).entries\n const ids = new Set(Object.keys(entries))\n const headings = indexHeadings(entries)\n const titles = new Set(Object.values(entries).flatMap((e) => (e.title ? [e.title] : [])))\n const dead = linkProblems(registry, ids)\n const count = registry.entries.reduce((n, entry) => n + (entry.storyId ? 1 : 0) + (entry.docsId ? 1 : 0), 0)\n if (dead.length) {\n process.stderr.write(`story links: ${dead.length} of ${count} lead nowhere:\\n ${dead.join('\\n ')}\\n`)\n return { ok: false, headings, titles }\n }\n process.stdout.write(`story links: all ${count} open a story or docs page Storybook has\\n`)\n return { ok: true, headings, titles }\n}\n\n/**\n * What a part draws only when a prop is passed, where no story passes it\n * (UIG-19, `./gated`). Strict, by Katerina's ruling: a part's own stories show\n * everything it can draw. In an app it reads the reusable parts; in the package,\n * every component (`kinds` is the package's one class, `component`).\n */\nasync function checkGated(registry: Registry, folder: string, kinds?: string[]): Promise<boolean> {\n const { gatedFindings } = await gatedScan()\n const found = gatedFindings(registry, folder, { ...(kinds ? { kinds } : {}), pictured: 'own' })\n if (found.length) {\n const lines = found.map((f) => `${f.part} (${f.file}) draws something only when given \\`${f.prop}\\`, and no story that draws it passes \\`${f.prop}\\`: pass it in a story`)\n process.stderr.write(`stories: ${found.length === 1 ? 'one thing a part draws is' : `${found.length} things parts draw are`} never shown:\\n ${lines.join('\\n ')}\\n`)\n return false\n }\n process.stdout.write('stories: everything a part draws behind a prop is shown by a story\\n')\n return true\n}\n\n/**\n * The repository's hand-written map of its Storybook \u2014 the heading order and the\n * Introduction \u2014 against the Storybook (its own index, when there is one) and,\n * in an app, the catalogue (`./storymap`). Reports, and says whether it passed.\n */\nfunction checkStoryMap(folder: string, registry?: Registry, headings?: Set<string>): boolean {\n const problems = storyMapProblems(folder, { ...(registry ? { registry } : {}), ...(headings ? { real: headings } : {}) })\n if (problems.length) {\n process.stderr.write(`storybook map: ${problems.length === 1 ? 'one line no longer matches' : `${problems.length} lines no longer match`}:\\n ${problems.join('\\n ')}\\n`)\n return false\n }\n process.stdout.write('storybook map: the heading order and the Introduction match the Storybook and the catalogue\\n')\n return true\n}\n\n/** One line per kind, in a fixed order, so two runs read the same. */\nfunction summary(registry: Registry): string {\n const count = (cls: string) => registry.entries.filter((entry) => entry.app?.class === cls).length\n const kinds = CLASSES.map((cls) => `${count(cls)} ${cls}`).join(', ')\n const files = registry.builtFrom.files ?? 0\n return `${registry.builtFrom.repo}: ${registry.entries.length} parts in ${files - registry.filesWithoutParts.length} files (${registry.filesWithoutParts.length} more hold none) \u2014 ${kinds}. Every part is described and sorted.`\n}\n\nasync function main(): Promise<number> {\n switch (command) {\n case 'find': {\n const query = plain().join(' ')\n if (!query.trim()) {\n process.stderr.write('estiva-ui find <words\u2026> \u2014 what the thing you need does, in words\\n')\n return 1\n }\n const registries: Registry[] = [readPackageRegistry()]\n // An app that cannot be read is said out loud, and the rest still answer.\n const add = async (folder: string, repo: string | undefined) => {\n const behind = behindBy(folder)\n if (behind) process.stderr.write(`note: ${repo ?? basename(folder)} (${folder}) is ${behind} commit${behind === 1 ? '' : 's'} behind main as last fetched \u2014 its answers may name parts that are gone; pull it\\n`)\n try {\n registries.push(await buildApp(folder, repo))\n } catch (error) {\n const [first, ...more] = String(error instanceof Error ? error.message : error).split('\\n')\n process.stderr.write(`not searched: ${first}${more.length ? `\\n${more.slice(0, 3).join('\\n')}${more.length > 3 ? `\\n \u2026and ${more.length - 3} more \u2014 run \"estiva-ui check\" there` : ''}` : ''}\\n`)\n }\n }\n // The folder it runs in is searched when it is an app; the folder that\n // holds the repositories is not one, and only its neighbours answer.\n const here = !isPackage && existsSync(resolve(root, 'package.json')) ? (manifestAt(root).name ?? basename(root)) : null\n if (here) await add(root, value('repo'))\n if (!values('also').length && !flag('here')) {\n for (const sibling of findSiblings(workspaceOf(root), { skip: here ? [here] : [] })) await add(sibling.folder, sibling.name)\n }\n for (const also of values('also')) {\n const [name, folder] = also.includes('=') ? [also.slice(0, also.indexOf('=')), also.slice(also.indexOf('=') + 1)] : [undefined, also]\n const at = resolve(root, folder)\n if (!existsSync(resolve(at, 'package.json'))) {\n process.stderr.write(`not searched: ${name ?? basename(at)} \u2014 nothing at ${at}\\n`)\n continue\n }\n await add(at, name)\n }\n const limit = Number(value('limit') ?? 5)\n const findings = findInRegistries(registries, query, { limit: Number.isFinite(limit) && limit > 0 ? limit : 5 })\n if (flag('json')) {\n process.stdout.write(`${JSON.stringify(findings.map((finding) => finding.entry), null, 2)}\\n`)\n return 0\n }\n process.stdout.write(`${formatFindings(registries, findings, query)}\\n`)\n // Nothing found is not a failure \u2014 it is the answer that sends a session\n // to ask rather than to invent.\n return 0\n }\n\n case 'build': {\n const out = resolve(root, value('out') ?? 'registry.json')\n if (isPackage) {\n const { buildRegistry, serializeRegistry } = await packageBuilder()\n const registry = buildRegistry({ root })\n const problems = validateRegistry(registry)\n if (problems.length) {\n process.stderr.write(`the registry this build produced does not match its own schema:\\n ${problems.join('\\n ')}\\n`)\n return 1\n }\n writeFileSync(out, serializeRegistry(registry), 'utf8')\n process.stdout.write(`${out}: ${registry.entries.length} entries from ${registry.builtFrom.exports} exports\\n`)\n return 0\n }\n const { serializeRegistry } = await packageBuilder()\n const registry = await buildApp(root, value('repo'))\n writeFileSync(out, serializeRegistry(registry), 'utf8')\n process.stdout.write(`${out}: ${summary(registry)}\\n`)\n return 0\n }\n\n case 'check': {\n if (!isPackage) {\n const registry = await buildApp(root, value('repo'))\n process.stdout.write(`${summary(registry)}\\n`)\n // The usage-page contract and the story links (UIG-19): the one copy, run by every app's `gate` job.\n // A When not may name a part of the package or of this app (B8).\n const pkg = readPackageRegistry()\n const known = nameableParts(registry, pkg)\n const broken = contractProblems(registry, root, known)\n if (broken.length) {\n process.stderr.write(`${broken.length === 1 ? 'a part breaks' : `${broken.length} parts break`} the usage-page contract:\\n ${broken.join('\\n ')}\\n`)\n } else {\n const owed = registry.entries.filter((entry) => (CONTRACT_KINDS as readonly string[]).includes(entry.app?.class ?? '')).length\n process.stdout.write(`usage pages: all ${owed} reusable parts keep the contract and are drawn somewhere\\n`)\n }\n const linked = checkLinks(registry, root)\n const shown = await checkGated(registry, root)\n const loaded = checkLoader()\n const mapped = checkStoryMap(root, registry, linked.headings)\n // What the pages name must still be there (R14): parts under When and When not,\n // and the stories a Seen in line sends a reader to, here or in the package.\n const stale = nameProblems(registry, root, {\n known,\n ...(linked.titles ? { titles: linked.titles } : {}),\n packageIds: pkg.entries.flatMap((e) => [e.storyId, e.docsId].filter((id): id is string => Boolean(id))),\n })\n if (stale.length) process.stderr.write(`usage pages: ${stale.length === 1 ? 'one name leads' : `${stale.length} names lead`} nowhere:\\n ${stale.join('\\n ')}\\n`)\n else process.stdout.write('usage pages: every part and story they name is there\\n')\n return broken.length || stale.length || !linked.ok || !shown || !loaded || !mapped ? 1 : 0\n }\n const { buildRegistry, serializeRegistry } = await packageBuilder()\n const path = packageRegistryPath()\n const built = buildRegistry({ root })\n const problems = validateRegistry(built)\n if (problems.length) {\n process.stderr.write(`the registry does not match its own schema:\\n ${problems.join('\\n ')}\\n`)\n return 1\n }\n if (!existsSync(path)) {\n process.stderr.write(`${path} is missing \u2014 run \"npm run registry\"\\n`)\n return 1\n }\n // Byte for byte. The point of the check is that the committed file cannot\n // drift from the code; \"near enough\" is drift.\n if (readFileSync(path, 'utf8').replace(/\\r\\n/g, '\\n') !== serializeRegistry(built)) {\n process.stderr.write(`${path} is not what the code produces \u2014 run \"npm run registry\" and commit the result\\n`)\n return 1\n }\n process.stdout.write(`${path}: current, ${built.entries.length} entries from ${built.builtFrom.exports} exports\\n`)\n const linked = checkLinks(built, root)\n const shown = await checkGated(built, root, ['component'])\n const loaded = checkLoader()\n // The package keeps a heading order too (R16): it left out Layout, and nothing saw.\n const mapped = checkStoryMap(root, undefined, linked.headings)\n return linked.ok && shown && loaded && mapped ? 0 : 1\n }\n\n case 'skill': {\n const out = value('out')\n const folder = out ? resolve(out) : repositoryOf(root)\n // The loader's path starts where the session does and cannot climb out:\n // Claude Code refuses a `..` in it.\n if (relative(folder, packageRoot()).startsWith('..')) {\n process.stderr.write(`${folder} does not hold ${packageRoot()}: a loader there could not reach the skill's text\\n`)\n return 1\n }\n const path = loaderPath(folder)\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, loaderText(folder, packageRoot()), 'utf8')\n process.stdout.write(`${path}: the Claude skill, read from ${packageRoot()}\\n`)\n const settings = settingsWithSearch(folder)\n if (settings) {\n writeFileSync(settingsPath(folder), settings, 'utf8')\n process.stdout.write(`${settingsPath(folder)}: the search allowed without a prompt\\n`)\n }\n return 0\n }\n\n default:\n process.stderr.write('estiva-ui <find | build | check | skill>\\n')\n return 1\n }\n}\n\ntry {\n process.exitCode = await main()\n} catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n}\n", "/// <reference types=\"node\" />\n/**\n * The apps beside this one, found rather than named (UIG-20).\n *\n * `estiva-ui find` with nothing else typed searches the package, the app it is\n * run in, and every app that sits beside it. Katerina's condition for having no\n * shared Storybook is that one search, run unasked from any repository, covers\n * every catalogue; a list of names written into each repository's script would\n * be the drifting copy the gates exist to remove, and would miss the next app.\n *\n * An app is a folder, or one folder inside it, whose `package.json` depends on\n * `@estiva-app/ui` and which has a `src` to read. A service that does not\n * install the package is not an app of this catalogue until it does.\n *\n * Each app is searched once. A second checkout of the same app (a worktree, or\n * somebody's copy) carries the same package name; the app the search runs in\n * wins, then the one whose top folder has the shortest name.\n */\nimport { execFileSync } from 'node:child_process'\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'\nimport { basename, dirname, join, resolve } from 'node:path'\n\nexport const PACKAGE_NAME = '@estiva-app/ui'\n\nexport interface Sibling {\n /** What its entries are called: its top folder's name. */\n name: string\n /** The folder the catalogue is built from. */\n folder: string\n /** Its `package.json` name, which says two checkouts are one app. */\n packageName: string\n}\n\nfunction manifest(folder: string): { name?: string; dependencies?: Record<string, string>; devDependencies?: Record<string, string> } | null {\n const file = join(folder, 'package.json')\n if (!existsSync(file)) return null\n try {\n return JSON.parse(readFileSync(file, 'utf8'))\n } catch {\n return null\n }\n}\n\n/** The name of the app in `folder`, or null when the folder is not one. */\nexport function appName(folder: string): string | null {\n const m = manifest(folder)\n if (!m || m.name === PACKAGE_NAME) return null\n const uses = Boolean(m.dependencies?.[PACKAGE_NAME] ?? m.devDependencies?.[PACKAGE_NAME])\n return uses && existsSync(join(folder, 'src')) ? (m.name ?? basename(folder)) : null\n}\n\nconst isDir = (path: string) => {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n\n/**\n * The folder that holds the repositories: the parent of the repository `from`\n * is in, or `from` itself when it is in none (a folder that only holds them).\n */\nexport function workspaceOf(from: string): string {\n let at = resolve(from)\n for (;;) {\n if (existsSync(join(at, '.git'))) return dirname(at)\n const up = dirname(at)\n if (up === at) return resolve(from)\n at = up\n }\n}\n\n/** Every app in `workspace`, one per package name, except those named in `skip`. */\nexport function findSiblings(workspace: string, { skip = [] }: { skip?: string[] } = {}): Sibling[] {\n let tops: string[]\n try {\n tops = readdirSync(workspace).filter((name) => !name.startsWith('.') && name !== 'node_modules' && isDir(join(workspace, name)))\n } catch {\n return []\n }\n const found: Sibling[] = []\n for (const top of tops) {\n const folder = join(workspace, top)\n const inside = readdirSync(folder).filter((name) => !name.startsWith('.') && name !== 'node_modules' && isDir(join(folder, name)))\n for (const candidate of [folder, ...inside.map((name) => join(folder, name))]) {\n const packageName = appName(candidate)\n if (packageName) found.push({ name: top, folder: candidate, packageName })\n }\n }\n const byPackage = new Map<string, Sibling>()\n for (const sibling of found.sort((a, b) => a.name.length - b.name.length || a.name.localeCompare(b.name))) {\n if (skip.includes(sibling.packageName) || byPackage.has(sibling.packageName)) continue\n byPackage.set(sibling.packageName, sibling)\n }\n return [...byPackage.values()].sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/**\n * How many commits the checkout that holds `folder` is behind its remote's main,\n * as last fetched: nothing is fetched, so a search stays quick. Null when it is\n * not a git checkout or has no remote main. A search that reads a stale copy\n * answers with parts that are gone (the re-review after the audit before UIG-26:\n * \"files panel\" found Peek's deleted FilesPanel in an old checkout), so `find`\n * says so beside its answer.\n */\nexport function behindBy(folder: string): number | null {\n const git = (...a: string[]) => {\n try {\n return execFileSync('git', ['-C', folder, ...a], { stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 }).toString().trim()\n } catch {\n return null\n }\n }\n const main = git('symbolic-ref', '--short', 'refs/remotes/origin/HEAD') ?? (git('rev-parse', '--verify', '--quiet', 'origin/main') ? 'origin/main' : null)\n if (!main) return null\n const count = Number(git('rev-list', '--count', `HEAD..${main}`))\n return Number.isFinite(count) ? count : null\n}\n", "/// <reference types=\"node\" />\n/**\n * The Claude skill (UIG-20): search the catalogue before building anything.\n *\n * Its text lives once, in this package, as `skill/estiva-ui.md` (Katerina,\n * 17 September 2026: the same gate text pasted into two repositories is a\n * defect). Claude Code only loads a skill from a `SKILL.md` under a folder's\n * `.claude/skills/`, and no plugin source can point into `node_modules`, so each\n * repository commits a loader: the trigger below, and one line that reads the\n * installed package's text into the skill when it opens (`` !`\u2026` `` runs before\n * Claude sees the skill). `estiva-ui skill` writes it; `estiva-ui check` fails\n * when a repository's loader is missing or is not what this writes.\n *\n * Measured with fresh sessions of Claude Code 2.1.215 (UIG-20), three things\n * shape that line and the settings beside it:\n *\n * - An injected command runs without a prompt only when Claude Code can read it\n * statically as read-only: a plain `cat` of a plain path. A path with `..`\n * (\"may follow a symlink outside the working directory\"), a `$(\u2026)`, or a\n * `node -e` are all refused, and a refused command stops the skill opening.\n * So the path starts at `${CLAUDE_PROJECT_DIR}`, the folder the session\n * started in, as the editor gate's hook does: a session started above or\n * below the repository gets neither.\n * - An `allowed-tools` line makes opening the skill itself ask for permission\n * (\"Execute skill\"). So the search is allowed in the repository's committed\n * `.claude/settings.json` instead, and the skill carries no `allowed-tools`.\n * - Claude reads a skill's description before it opens the skill, so the\n * trigger is written into the loader; it is the one part that cannot be\n * pulled in later.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join, relative, resolve, sep } from 'node:path'\n\nexport const SKILL_NAME = 'estiva-ui'\n\n/** When Claude opens the skill by itself. Kept under Claude Code's 1,536 characters. */\nexport const SKILL_DESCRIPTION =\n 'Use before building, adding or changing anything on screen in an app that uses @estiva-app/ui, or in @estiva-app/ui itself: ' +\n 'a screen, panel, list, row, header, dialog, menu, empty state, a new component, or how something looks. ' +\n 'Also when someone asks for a component in plain words (\"I need a\u2026\", \"add a\u2026\", \"is there a\u2026\"). ' +\n \"It searches every catalogue at once, the package's and each app's Storybook, for a part that already does the job, \" +\n 'and lists the mistakes this codebase keeps making.'\n\n/** The search, allowed without a prompt in the folder's committed `.claude/settings.json`. */\nexport const SEARCH_RULES = ['Bash(npm run ui:find *)', 'Bash(npm --prefix * run ui:find *)', 'PowerShell(npm run ui:find *)', 'PowerShell(npm --prefix * run ui:find *)']\n\n/** The skill's text, inside a package folder. */\nexport const skillTextPath = (packageRoot: string) => join(packageRoot, 'skill', `${SKILL_NAME}.md`)\n\n/** Where a folder keeps its loader. */\nexport const loaderPath = (folder: string) => join(folder, '.claude', 'skills', SKILL_NAME, 'SKILL.md')\n\n/** Where a folder keeps the settings that allow the search. */\nexport const settingsPath = (folder: string) => join(folder, '.claude', 'settings.json')\n\n/** The loader for `folder`, reading the text from `packageRoot`, from where a session in `folder` starts. */\nexport function loaderText(folder: string, packageRoot: string): string {\n const fromTop = relative(folder, skillTextPath(packageRoot)).split(sep).join('/')\n return [\n '---',\n `name: ${SKILL_NAME}`,\n `description: ${JSON.stringify(SKILL_DESCRIPTION)}`,\n '---',\n '',\n '<!-- Written by `estiva-ui skill` from @estiva-app/ui; `estiva-ui check` fails when it differs. The text is the installed package\u2019s, read in below. -->',\n '',\n `!\\`cat \"\\${CLAUDE_PROJECT_DIR}/${fromTop}\"\\``,\n '',\n ].join('\\n')\n}\n\ntype Settings = { permissions?: { allow?: string[] } & Record<string, unknown> } & Record<string, unknown>\n\nconst readSettings = (folder: string): Settings | null => {\n const path = settingsPath(folder)\n if (!existsSync(path)) return {}\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as Settings\n } catch {\n return null\n }\n}\n\n/** `folder`'s settings with the search allowed, everything else as it was; null when they cannot be read. */\nexport function settingsWithSearch(folder: string): string | null {\n const settings = readSettings(folder)\n if (!settings) return null\n const allow = settings.permissions?.allow ?? []\n const missing = SEARCH_RULES.filter((rule) => !allow.includes(rule))\n if (!missing.length && existsSync(settingsPath(folder))) return null\n settings.permissions = { ...settings.permissions, allow: [...allow, ...missing] }\n return `${JSON.stringify(settings, null, 2)}\\n`\n}\n\n/** The repository `from` is in: the nearest folder with `.git`, or `from` when there is none. */\nexport function repositoryOf(from: string): string {\n let at = resolve(from)\n for (;;) {\n if (existsSync(join(at, '.git'))) return at\n const up = dirname(at)\n if (up === at) return resolve(from)\n at = up\n }\n}\n\n/** What is wrong with `folder`'s loader, or null when it is what `loaderText` writes. */\nexport function loaderProblem(folder: string, packageRoot: string): string | null {\n const path = loaderPath(folder)\n const shown = relative(process.cwd(), path).split(sep).join('/') || path\n if (!existsSync(path)) return `${shown} is missing: run \"estiva-ui skill\" and commit it`\n if (readFileSync(path, 'utf8').replace(/\\r\\n/g, '\\n') !== loaderText(folder, packageRoot)) return `${shown} is not what the package writes: run \"estiva-ui skill\" and commit it`\n if (!existsSync(skillTextPath(packageRoot))) return `${shown} reads ${skillTextPath(packageRoot)}, which is not there`\n const allow = readSettings(folder)?.permissions?.allow ?? []\n const missing = SEARCH_RULES.filter((rule) => !allow.includes(rule))\n if (missing.length) return `.claude/settings.json does not allow the search (${missing.join(', ')}): run \"estiva-ui skill\" and commit it`\n return null\n}\n", "/// <reference types=\"node\" />\n/**\n * The hand-written map of a Storybook, held against the Storybook it describes\n * (moved here from Peek's own status checks, audit A3; widened by the re-review\n * after it, R16).\n *\n * A repository may keep two maps of its sidebar: the order of its top-level\n * headings in `.storybook/preview.tsx` \u2014 a `const HEADINGS = [...]` list read by\n * a `storySort` function (Peek), or `storySort: { order: [...] }` (Ship, this\n * package) \u2014 and the Introduction page, which names each heading, in a table row\n * (`| **Folders** | \u2026 |`, Peek) or a bullet (`- **Primitives \u00B7 Inputs** \u2014 \u2026`,\n * Ship), and in an app states how many parts are worth reusing. Both are written\n * by hand and both describe something that moves, so both rot: Peek's named a\n * Huddles heading for a day after its stories were deleted and said 49 reusable\n * parts when the catalogue built 41; Ship's named a Feedback heading it does not\n * have and left out four it does.\n *\n * The real headings are read from Storybook's own index when the command has\n * one, so a title written any way at all counts; otherwise from the story files.\n * Every check runs only when the repository states the thing it checks.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Registry } from './schema'\n\nconst PREVIEW = '.storybook/preview.tsx'\nconst INTRODUCTIONS = ['src/stories/Introduction.mdx', 'stories/Introduction.mdx']\n/** The classes an app's Introduction counts as \"worth reusing\". */\nconst WORTH_REUSING = ['reusable', 'promote-candidate']\n\nfunction storyFiles(root: string): string[] {\n const out: string[] = []\n const walk = (dir: string) => {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n const path = join(dir, entry.name)\n if (entry.isDirectory()) walk(path)\n else if (entry.name.endsWith('.stories.tsx') || entry.name.endsWith('.mdx')) out.push(path)\n }\n }\n walk(join(root, 'src'))\n walk(join(root, 'stories'))\n return out\n}\n\n/** Every top-level heading the sidebar has, read from the story files: the first segment of each title. */\nexport function sidebarHeadings(root: string): Set<string> {\n const real = new Set<string>()\n for (const file of storyFiles(root)) {\n const text = readFileSync(file, 'utf8')\n const start = text.indexOf('const meta')\n const meta = start === -1 ? '' : text.slice(start, text.indexOf('satisfies Meta', start) + 1 || undefined)\n const title = meta.match(/\\btitle: *['\"]([^'\"]+)['\"]/) ?? text.match(/<Meta\\s+title=['\"]([^'\"]+)['\"]/)\n if (title) real.add(title[1].split('/')[0].trim())\n }\n return real\n}\n\n/** The top-level headings of Storybook's own index (`storybook index`): the first segment of every entry's title. */\nexport function indexHeadings(entries: Record<string, { title?: string }>): Set<string> {\n return new Set(Object.values(entries).flatMap((e) => (e.title ? [e.title.split('/')[0].trim()] : [])))\n}\n\n/** The strings at the top level of the array that starts at `open` (the index of its `[`), skipping nested arrays. */\nfunction topLevelStrings(text: string, open: number): string[] {\n const out: string[] = []\n let depth = 0\n for (let i = open; i < text.length; i++) {\n const c = text[i]\n if (c === '/' && text[i + 1] === '*') {\n const end = text.indexOf('*/', i + 2)\n if (end < 0) break\n i = end + 1\n } else if (c === '/' && text[i + 1] === '/') i = text.indexOf('\\n', i)\n else if (c === '[') depth++\n else if (c === ']') {\n if (--depth === 0) return out\n } else if (c === \"'\" || c === '\"') {\n const end = text.indexOf(c, i + 1)\n if (depth === 1) out.push(text.slice(i + 1, end))\n i = end\n }\n if (i < 0) break\n }\n return out\n}\n\n/** The headings the preview orders \u2014 a `HEADINGS` list or `storySort: { order: [...] }` \u2014 or null when it keeps no order. */\nexport function orderedHeadings(previewText: string): string[] | null {\n const list = /const HEADINGS = \\[/.exec(previewText) ?? /\\border:\\s*\\[/.exec(previewText)\n if (!list) return null\n return topLevelStrings(previewText, list.index + list[0].length - 1)\n}\n\n/**\n * The headings the Introduction names: a table row that opens with a bold name,\n * or a bullet that opens with bold names joined by \" \u00B7 \".\n */\nexport function introductionHeadings(introText: string): Set<string> {\n const rows = [...introText.matchAll(/^\\|\\s*\\*\\*([^*]+)\\*\\*\\s*\\|/gm)].map((m) => m[1])\n const bullets = [...introText.matchAll(/^[-*]\\s+\\*\\*([^*]+)\\*\\*\\s*[\u2014\u2013-]/gm)].flatMap((m) => m[1].split('\u00B7'))\n return new Set([...rows, ...bullets].map((h) => h.trim()).filter(Boolean))\n}\n\n/** The number the Introduction states, or null when it states none. */\nexport function introductionCount(introText: string): number | null {\n const said = introText.match(/\\*\\*(\\d+) parts worth reusing\\*\\*/)\n return said ? Number(said[1]) : null\n}\n\n/**\n * What is wrong with the repository's hand-written map of its Storybook; empty\n * when nothing is. `real` is the sidebar's top-level headings from Storybook's\n * index, when the caller has it; `registry` is needed only for an app's count.\n */\nexport function storyMapProblems(root: string, { registry, real = sidebarHeadings(root) }: { registry?: Registry; real?: Set<string> } = {}): string[] {\n const problems: string[] = []\n const previewPath = join(root, PREVIEW)\n if (existsSync(previewPath)) {\n const ordered = orderedHeadings(readFileSync(previewPath, 'utf8'))\n if (ordered) {\n const unordered = [...real].filter((r) => !ordered.includes(r))\n const empty = ordered.filter((o) => !real.has(o))\n if (unordered.length) problems.push(`${PREVIEW}: not in the heading order, so they fall to the bottom of the sidebar: ${unordered.join(', ')}`)\n if (empty.length) problems.push(`${PREVIEW}: in the heading order but no story uses them: ${empty.join(', ')}`)\n }\n }\n const intro = INTRODUCTIONS.find((p) => existsSync(join(root, p)))\n if (intro) {\n const text = readFileSync(join(root, intro), 'utf8')\n const named = introductionHeadings(text)\n if (named.size) {\n const missing = [...real].filter((r) => !named.has(r))\n const stale = [...named].filter((n) => !real.has(n))\n if (missing.length) problems.push(`${intro}: the sidebar has, the page does not name: ${missing.join(', ')}`)\n if (stale.length) problems.push(`${intro}: the page names, the sidebar does not have: ${stale.join(', ')}`)\n }\n const said = introductionCount(text)\n if (said !== null && registry) {\n const reusable = registry.entries.filter((entry) => WORTH_REUSING.includes(entry.app?.class ?? '')).length\n if (said !== reusable) problems.push(`${intro}: says ${said} parts worth reusing; the catalogue builds ${reusable}`)\n }\n }\n return problems\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;AAiCA,SAAS,iBAAiB;AAC1B,SAAS,cAAAA,aAAY,WAAW,aAAa,gBAAAC,eAAc,qBAAqB;AAChF,SAAS,cAAc;AACvB,SAAS,YAAAC,WAAU,WAAAC,UAAS,QAAAC,OAAM,YAAAC,WAAU,WAAAC,gBAAe;AAC3D,SAAS,qBAAqB;;;ACnB9B,SAAS,oBAAoB;AAC7B,SAAS,YAAY,aAAa,cAAc,gBAAgB;AAChE,SAAS,UAAU,SAAS,MAAM,eAAe;AAE1C,IAAM,eAAe;AAW5B,SAAS,SAAS,QAA2H;AAC3I,QAAM,OAAO,KAAK,QAAQ,cAAc;AACxC,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,QAAQ,QAA+B;AACrD,QAAM,IAAI,SAAS,MAAM;AACzB,MAAI,CAAC,KAAK,EAAE,SAAS,aAAc,QAAO;AAC1C,QAAM,OAAO,QAAQ,EAAE,eAAe,YAAY,KAAK,EAAE,kBAAkB,YAAY,CAAC;AACxF,SAAO,QAAQ,WAAW,KAAK,QAAQ,KAAK,CAAC,IAAK,EAAE,QAAQ,SAAS,MAAM,IAAK;AAClF;AAEA,IAAM,QAAQ,CAAC,SAAiB;AAC9B,MAAI;AACF,WAAO,SAAS,IAAI,EAAE,YAAY;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,YAAY,MAAsB;AAChD,MAAI,KAAK,QAAQ,IAAI;AACrB,aAAS;AACP,QAAI,WAAW,KAAK,IAAI,MAAM,CAAC,EAAG,QAAO,QAAQ,EAAE;AACnD,UAAM,KAAK,QAAQ,EAAE;AACrB,QAAI,OAAO,GAAI,QAAO,QAAQ,IAAI;AAClC,SAAK;AAAA,EACP;AACF;AAGO,SAAS,aAAa,WAAmB,EAAE,OAAO,CAAC,EAAE,IAAyB,CAAC,GAAc;AAClG,MAAI;AACJ,MAAI;AACF,WAAO,YAAY,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW,GAAG,KAAK,SAAS,kBAAkB,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC;AAAA,EACjI,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,QAAmB,CAAC;AAC1B,aAAW,OAAO,MAAM;AACtB,UAAM,SAAS,KAAK,WAAW,GAAG;AAClC,UAAM,SAAS,YAAY,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW,GAAG,KAAK,SAAS,kBAAkB,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC;AACjI,eAAW,aAAa,CAAC,QAAQ,GAAG,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG;AAC7E,YAAM,cAAc,QAAQ,SAAS;AACrC,UAAI,YAAa,OAAM,KAAK,EAAE,MAAM,KAAK,QAAQ,WAAW,YAAY,CAAC;AAAA,IAC3E;AAAA,EACF;AACA,QAAM,YAAY,oBAAI,IAAqB;AAC3C,aAAW,WAAW,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC,GAAG;AACzG,QAAI,KAAK,SAAS,QAAQ,WAAW,KAAK,UAAU,IAAI,QAAQ,WAAW,EAAG;AAC9E,cAAU,IAAI,QAAQ,aAAa,OAAO;AAAA,EAC5C;AACA,SAAO,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAC5E;AAUO,SAAS,SAAS,QAA+B;AACtD,QAAM,MAAM,IAAI,MAAgB;AAC9B,QAAI;AACF,aAAO,aAAa,OAAO,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,UAAU,QAAQ,QAAQ,GAAG,SAAS,IAAM,CAAC,EAAE,SAAS,EAAE,KAAK;AAAA,IAC5H,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAMC,QAAO,IAAI,gBAAgB,WAAW,0BAA0B,MAAM,IAAI,aAAa,YAAY,WAAW,aAAa,IAAI,gBAAgB;AACrJ,MAAI,CAACA,MAAM,QAAO;AAClB,QAAM,QAAQ,OAAO,IAAI,YAAY,WAAW,SAASA,KAAI,EAAE,CAAC;AAChE,SAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAC1C;;;ACxFA,SAAS,cAAAC,aAAY,gBAAAC,qBAAoB;AACzC,SAAS,WAAAC,UAAS,QAAAC,OAAM,UAAU,WAAAC,UAAS,WAAW;AAE/C,IAAM,aAAa;AAGnB,IAAM,oBACX;AAOK,IAAM,eAAe,CAAC,2BAA2B,sCAAsC,iCAAiC,0CAA0C;AAGlK,IAAM,gBAAgB,CAACC,iBAAwBF,MAAKE,cAAa,SAAS,GAAG,UAAU,KAAK;AAG5F,IAAM,aAAa,CAAC,WAAmBF,MAAK,QAAQ,WAAW,UAAU,YAAY,UAAU;AAG/F,IAAM,eAAe,CAAC,WAAmBA,MAAK,QAAQ,WAAW,eAAe;AAGhF,SAAS,WAAW,QAAgBE,cAA6B;AACtE,QAAM,UAAU,SAAS,QAAQ,cAAcA,YAAW,CAAC,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AAChF,SAAO;AAAA,IACL;AAAA,IACA,SAAS,UAAU;AAAA,IACnB,gBAAgB,KAAK,UAAU,iBAAiB,CAAC;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,kCAAkC,OAAO;AAAA,IACzC;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAIA,IAAM,eAAe,CAAC,WAAoC;AACxD,QAAM,OAAO,aAAa,MAAM;AAChC,MAAI,CAACL,YAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,MAAI;AACF,WAAO,KAAK,MAAMC,cAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,mBAAmB,QAA+B;AAChE,QAAM,WAAW,aAAa,MAAM;AACpC,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,QAAQ,SAAS,aAAa,SAAS,CAAC;AAC9C,QAAM,UAAU,aAAa,OAAO,CAAC,SAAS,CAAC,MAAM,SAAS,IAAI,CAAC;AACnE,MAAI,CAAC,QAAQ,UAAUD,YAAW,aAAa,MAAM,CAAC,EAAG,QAAO;AAChE,WAAS,cAAc,EAAE,GAAG,SAAS,aAAa,OAAO,CAAC,GAAG,OAAO,GAAG,OAAO,EAAE;AAChF,SAAO,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA;AAC7C;AAGO,SAAS,aAAa,MAAsB;AACjD,MAAI,KAAKI,SAAQ,IAAI;AACrB,aAAS;AACP,QAAIJ,YAAWG,MAAK,IAAI,MAAM,CAAC,EAAG,QAAO;AACzC,UAAM,KAAKD,SAAQ,EAAE;AACrB,QAAI,OAAO,GAAI,QAAOE,SAAQ,IAAI;AAClC,SAAK;AAAA,EACP;AACF;AAGO,SAAS,cAAc,QAAgBC,cAAoC;AAChF,QAAM,OAAO,WAAW,MAAM;AAC9B,QAAM,QAAQ,SAAS,QAAQ,IAAI,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG,KAAK;AACpE,MAAI,CAACL,YAAW,IAAI,EAAG,QAAO,GAAG,KAAK;AACtC,MAAIC,cAAa,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW,QAAQI,YAAW,EAAG,QAAO,GAAG,KAAK;AAC1G,MAAI,CAACL,YAAW,cAAcK,YAAW,CAAC,EAAG,QAAO,GAAG,KAAK,UAAU,cAAcA,YAAW,CAAC;AAChG,QAAM,QAAQ,aAAa,MAAM,GAAG,aAAa,SAAS,CAAC;AAC3D,QAAM,UAAU,aAAa,OAAO,CAAC,SAAS,CAAC,MAAM,SAAS,IAAI,CAAC;AACnE,MAAI,QAAQ,OAAQ,QAAO,oDAAoD,QAAQ,KAAK,IAAI,CAAC;AACjG,SAAO;AACT;;;AC/FA,SAAS,cAAAC,aAAY,eAAAC,cAAa,gBAAAC,qBAAoB;AACtD,SAAS,QAAAC,aAAY;AAGrB,IAAM,UAAU;AAChB,IAAM,gBAAgB,CAAC,gCAAgC,0BAA0B;AAEjF,IAAM,gBAAgB,CAAC,YAAY,mBAAmB;AAEtD,SAAS,WAAWC,OAAwB;AAC1C,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,QAAgB;AAC5B,QAAI,CAACJ,YAAW,GAAG,EAAG;AACtB,eAAW,SAASC,aAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAC7D,UAAI,MAAM,SAAS,kBAAkB,MAAM,KAAK,WAAW,GAAG,EAAG;AACjE,YAAM,OAAOE,MAAK,KAAK,MAAM,IAAI;AACjC,UAAI,MAAM,YAAY,EAAG,MAAK,IAAI;AAAA,eACzB,MAAM,KAAK,SAAS,cAAc,KAAK,MAAM,KAAK,SAAS,MAAM,EAAG,KAAI,KAAK,IAAI;AAAA,IAC5F;AAAA,EACF;AACA,OAAKA,MAAKC,OAAM,KAAK,CAAC;AACtB,OAAKD,MAAKC,OAAM,SAAS,CAAC;AAC1B,SAAO;AACT;AAGO,SAAS,gBAAgBA,OAA2B;AACzD,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,QAAQ,WAAWA,KAAI,GAAG;AACnC,UAAM,OAAOF,cAAa,MAAM,MAAM;AACtC,UAAM,QAAQ,KAAK,QAAQ,YAAY;AACvC,UAAM,OAAO,UAAU,KAAK,KAAK,KAAK,MAAM,OAAO,KAAK,QAAQ,kBAAkB,KAAK,IAAI,KAAK,MAAS;AACzG,UAAM,QAAQ,KAAK,MAAM,4BAA4B,KAAK,KAAK,MAAM,gCAAgC;AACrG,QAAI,MAAO,MAAK,IAAI,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC;AAAA,EACnD;AACA,SAAO;AACT;AAGO,SAAS,cAAc,SAA0D;AACtF,SAAO,IAAI,IAAI,OAAO,OAAO,OAAO,EAAE,QAAQ,CAAC,MAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAE,CAAC;AACvG;AAGA,SAAS,gBAAgB,MAAc,MAAwB;AAC7D,QAAM,MAAgB,CAAC;AACvB,MAAI,QAAQ;AACZ,WAAS,IAAI,MAAM,IAAI,KAAK,QAAQ,KAAK;AACvC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,KAAK;AACpC,YAAM,MAAM,KAAK,QAAQ,MAAM,IAAI,CAAC;AACpC,UAAI,MAAM,EAAG;AACb,UAAI,MAAM;AAAA,IACZ,WAAW,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAK,KAAI,KAAK,QAAQ,MAAM,CAAC;AAAA,aAC5D,MAAM,IAAK;AAAA,aACX,MAAM,KAAK;AAClB,UAAI,EAAE,UAAU,EAAG,QAAO;AAAA,IAC5B,WAAW,MAAM,OAAO,MAAM,KAAK;AACjC,YAAM,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;AACjC,UAAI,UAAU,EAAG,KAAI,KAAK,KAAK,MAAM,IAAI,GAAG,GAAG,CAAC;AAChD,UAAI;AAAA,IACN;AACA,QAAI,IAAI,EAAG;AAAA,EACb;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,aAAsC;AACpE,QAAM,OAAO,sBAAsB,KAAK,WAAW,KAAK,gBAAgB,KAAK,WAAW;AACxF,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,gBAAgB,aAAa,KAAK,QAAQ,KAAK,CAAC,EAAE,SAAS,CAAC;AACrE;AAMO,SAAS,qBAAqB,WAAgC;AACnE,QAAM,OAAO,CAAC,GAAG,UAAU,SAAS,8BAA8B,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AACpF,QAAM,UAAU,CAAC,GAAG,UAAU,SAAS,mCAAmC,CAAC,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,MAAG,CAAC;AAC3G,SAAO,IAAI,IAAI,CAAC,GAAG,MAAM,GAAG,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC;AAC3E;AAGO,SAAS,kBAAkB,WAAkC;AAClE,QAAM,OAAO,UAAU,MAAM,mCAAmC;AAChE,SAAO,OAAO,OAAO,KAAK,CAAC,CAAC,IAAI;AAClC;AAOO,SAAS,iBAAiBE,OAAc,EAAE,UAAU,OAAO,gBAAgBA,KAAI,EAAE,IAAiD,CAAC,GAAa;AACrJ,QAAM,WAAqB,CAAC;AAC5B,QAAM,cAAcD,MAAKC,OAAM,OAAO;AACtC,MAAIJ,YAAW,WAAW,GAAG;AAC3B,UAAM,UAAU,gBAAgBE,cAAa,aAAa,MAAM,CAAC;AACjE,QAAI,SAAS;AACX,YAAM,YAAY,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,CAAC,CAAC;AAC9D,YAAM,QAAQ,QAAQ,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;AAChD,UAAI,UAAU,OAAQ,UAAS,KAAK,GAAG,OAAO,0EAA0E,UAAU,KAAK,IAAI,CAAC,EAAE;AAC9I,UAAI,MAAM,OAAQ,UAAS,KAAK,GAAG,OAAO,kDAAkD,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,IAChH;AAAA,EACF;AACA,QAAM,QAAQ,cAAc,KAAK,CAAC,MAAMF,YAAWG,MAAKC,OAAM,CAAC,CAAC,CAAC;AACjE,MAAI,OAAO;AACT,UAAM,OAAOF,cAAaC,MAAKC,OAAM,KAAK,GAAG,MAAM;AACnD,UAAM,QAAQ,qBAAqB,IAAI;AACvC,QAAI,MAAM,MAAM;AACd,YAAM,UAAU,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;AACrD,YAAM,QAAQ,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;AACnD,UAAI,QAAQ,OAAQ,UAAS,KAAK,GAAG,KAAK,8CAA8C,QAAQ,KAAK,IAAI,CAAC,EAAE;AAC5G,UAAI,MAAM,OAAQ,UAAS,KAAK,GAAG,KAAK,gDAAgD,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,IAC5G;AACA,UAAM,OAAO,kBAAkB,IAAI;AACnC,QAAI,SAAS,QAAQ,UAAU;AAC7B,YAAM,WAAW,SAAS,QAAQ,OAAO,CAAC,UAAU,cAAc,SAAS,MAAM,KAAK,SAAS,EAAE,CAAC,EAAE;AACpG,UAAI,SAAS,SAAU,UAAS,KAAK,GAAG,KAAK,UAAU,IAAI,8CAA8C,QAAQ,EAAE;AAAA,IACrH;AAAA,EACF;AACA,SAAO;AACT;;;AH3FA,IAAM,iBAAiB,MAAM,OAAO,qBAAS;AAC7C,IAAM,aAAa,MAAM,OAAO,mBAAO;AACvC,IAAM,YAAY,MAAM,OAAO,qBAAS;AAExC,IAAM,CAAC,SAAS,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC;AAC/C,IAAM,SAAS,CAAC,WAAW,SAAS,UAAU,UAAU,UAAU,QAAQ;AAC1E,IAAM,OAAO,CAAC,SAAiB,KAAK,SAAS,KAAK,IAAI,EAAE;AACxD,IAAM,QAAQ,CAAC,SAAiB;AAC9B,QAAM,KAAK,KAAK,QAAQ,KAAK,IAAI,EAAE;AACnC,SAAO,OAAO,KAAK,SAAY,KAAK,KAAK,CAAC;AAC5C;AACA,IAAM,SAAS,CAAC,SAAiB,KAAK,QAAQ,CAAC,MAAM,MAAO,SAAS,KAAK,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM,SAAY,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,CAAE;AAEnI,IAAM,QAAQ,MAAM;AAClB,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,KAAK,CAAC,EAAE,WAAW,IAAI,GAAG;AAC5B,UAAI,OAAO,SAAS,KAAK,CAAC,CAAC,EAAG,MAAK;AACnC;AAAA,IACF;AACA,QAAI,KAAK,KAAK,CAAC,CAAC;AAAA,EAClB;AACA,SAAO;AACT;AAEA,IAAM,OAAOC,SAAQ,MAAM,MAAM,KAAK,GAAG;AACzC,IAAM,aAAa,CAAC,WAAmB,KAAK,MAAMC,cAAaD,SAAQ,QAAQ,cAAc,GAAG,MAAM,CAAC;AAEvG,IAAM,YAAYE,YAAWF,SAAQ,MAAM,cAAc,CAAC,KAAK,WAAW,IAAI,EAAE,SAAS;AAGzF,SAAS,sBAA8B;AACrC,QAAM,QAAQ,MAAM,MAAM;AAC1B,MAAI,MAAO,QAAOA,SAAQ,KAAK;AAC/B,MAAI,UAAW,QAAOA,SAAQ,MAAM,eAAe;AAGnD,SAAO,cAAc,IAAI,IAAI,uBAAuB,YAAY,GAAG,CAAC;AACtE;AAEA,SAAS,sBAAgC;AACvC,QAAM,OAAO,oBAAoB;AACjC,MAAI,CAACE,YAAW,IAAI,EAAG,OAAM,IAAI,MAAM,uBAAuB,IAAI,oDAA+C;AACjH,QAAM,WAAW,KAAK,MAAMD,cAAa,MAAM,MAAM,CAAC;AACtD,QAAM,WAAW,iBAAiB,QAAQ;AAE1C,MAAI,SAAS,OAAQ,OAAM,IAAI,MAAM,GAAG,IAAI;AAAA,IAAkC,SAAS,KAAK,MAAM,CAAC,EAAE;AACrG,SAAO;AACT;AAGA,IAAM,cAAc,MAAO,YAAY,OAAOD,SAAQ,MAAM,gBAAgB,eAAe,IAAI;AAG/F,SAAS,cAAuB;AAC9B,QAAM,UAAU,cAAc,aAAa,IAAI,GAAG,YAAY,CAAC;AAC/D,MAAI,SAAS;AACX,YAAQ,OAAO,MAAM,iBAAiB,OAAO;AAAA,CAAI;AACjD,WAAO;AAAA,EACT;AACA,UAAQ,OAAO,MAAM,0DAAqD;AAC1E,SAAO;AACT;AAEA,eAAe,SAAS,QAAgB,MAA6C;AACnF,QAAM,EAAE,iBAAiB,IAAI,MAAM,WAAW;AAC9C,QAAM,WAAW,iBAAiB,EAAE,MAAM,QAAQ,KAAK,CAAC;AACxD,QAAM,WAAW,iBAAiB,QAAQ;AAC1C,MAAI,SAAS,OAAQ,OAAM,IAAI,MAAM,oBAAoB,SAAS,UAAU,IAAI;AAAA,IAAsC,SAAS,KAAK,MAAM,CAAC,EAAE;AAC7I,SAAO;AACT;AAUA,SAAS,WAAW,UAAoB,QAA+E;AACrH,QAAM,MAAMA,SAAQ,QAAQ,gBAAgB,QAAQ,QAAQ,aAAa,UAAU,kBAAkB,WAAW;AAChH,MAAI,CAACE,YAAWF,SAAQ,QAAQ,YAAY,CAAC,KAAK,CAACE,YAAW,GAAG,GAAG;AAClE,YAAQ,OAAO,MAAM,oDAAoD;AACzE,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AACA,QAAM,MAAMC,MAAK,YAAYA,MAAK,OAAO,GAAG,kBAAkB,CAAC,GAAG,YAAY;AAG9E,QAAM,MACJ,QAAQ,aAAa,UACjB,UAAU,IAAI,GAAG,eAAe,GAAG,aAAa,EAAE,KAAK,QAAQ,UAAU,QAAQ,OAAO,KAAK,CAAC,IAC9F,UAAU,KAAK,CAAC,SAAS,MAAM,KAAK,SAAS,GAAG,EAAE,KAAK,QAAQ,UAAU,OAAO,CAAC;AACvF,MAAI,IAAI,WAAW,KAAK,CAACD,YAAW,GAAG,GAAG;AACxC,YAAQ,OAAO,MAAM;AAAA,GAAoF,IAAI,UAAU,IAAI,UAAU,IAAI,KAAK,CAAC;AAAA,CAAI;AACnJ,WAAO,EAAE,IAAI,MAAM;AAAA,EACrB;AACA,QAAM,UAAW,KAAK,MAAMD,cAAa,KAAK,MAAM,CAAC,EAAsD;AAC3G,QAAM,MAAM,IAAI,IAAI,OAAO,KAAK,OAAO,CAAC;AACxC,QAAM,WAAW,cAAc,OAAO;AACtC,QAAM,SAAS,IAAI,IAAI,OAAO,OAAO,OAAO,EAAE,QAAQ,CAAC,MAAO,EAAE,QAAQ,CAAC,EAAE,KAAK,IAAI,CAAC,CAAE,CAAC;AACxF,QAAM,OAAO,aAAa,UAAU,GAAG;AACvC,QAAM,QAAQ,SAAS,QAAQ,OAAO,CAAC,GAAG,UAAU,KAAK,MAAM,UAAU,IAAI,MAAM,MAAM,SAAS,IAAI,IAAI,CAAC;AAC3G,MAAI,KAAK,QAAQ;AACf,YAAQ,OAAO,MAAM,gBAAgB,KAAK,MAAM,OAAO,KAAK;AAAA,IAAqB,KAAK,KAAK,MAAM,CAAC;AAAA,CAAI;AACtG,WAAO,EAAE,IAAI,OAAO,UAAU,OAAO;AAAA,EACvC;AACA,UAAQ,OAAO,MAAM,oBAAoB,KAAK;AAAA,CAA4C;AAC1F,SAAO,EAAE,IAAI,MAAM,UAAU,OAAO;AACtC;AAQA,eAAe,WAAW,UAAoB,QAAgB,OAAoC;AAChG,QAAM,EAAE,cAAc,IAAI,MAAM,UAAU;AAC1C,QAAM,QAAQ,cAAc,UAAU,QAAQ,EAAE,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAI,UAAU,MAAM,CAAC;AAC9F,MAAI,MAAM,QAAQ;AAChB,UAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,IAAI,uCAAuC,EAAE,IAAI,2CAA2C,EAAE,IAAI,wBAAwB;AACzK,YAAQ,OAAO,MAAM,YAAY,MAAM,WAAW,IAAI,8BAA8B,GAAG,MAAM,MAAM,wBAAwB;AAAA,IAAoB,MAAM,KAAK,MAAM,CAAC;AAAA,CAAI;AACrK,WAAO;AAAA,EACT;AACA,UAAQ,OAAO,MAAM,sEAAsE;AAC3F,SAAO;AACT;AAOA,SAAS,cAAc,QAAgB,UAAqB,UAAiC;AAC3F,QAAM,WAAW,iBAAiB,QAAQ,EAAE,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC,GAAI,GAAI,WAAW,EAAE,MAAM,SAAS,IAAI,CAAC,EAAG,CAAC;AACxH,MAAI,SAAS,QAAQ;AACnB,YAAQ,OAAO,MAAM,kBAAkB,SAAS,WAAW,IAAI,+BAA+B,GAAG,SAAS,MAAM,wBAAwB;AAAA,IAAQ,SAAS,KAAK,MAAM,CAAC;AAAA,CAAI;AACzK,WAAO;AAAA,EACT;AACA,UAAQ,OAAO,MAAM,+FAA+F;AACpH,SAAO;AACT;AAGA,SAAS,QAAQ,UAA4B;AAC3C,QAAM,QAAQ,CAAC,QAAgB,SAAS,QAAQ,OAAO,CAAC,UAAU,MAAM,KAAK,UAAU,GAAG,EAAE;AAC5F,QAAM,QAAQ,QAAQ,IAAI,CAAC,QAAQ,GAAG,MAAM,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,KAAK,IAAI;AACpE,QAAM,QAAQ,SAAS,UAAU,SAAS;AAC1C,SAAO,GAAG,SAAS,UAAU,IAAI,KAAK,SAAS,QAAQ,MAAM,aAAa,QAAQ,SAAS,kBAAkB,MAAM,WAAW,SAAS,kBAAkB,MAAM,2BAAsB,KAAK;AAC5L;AAEA,eAAe,OAAwB;AACrC,UAAQ,SAAS;AAAA,IACf,KAAK,QAAQ;AACX,YAAM,QAAQ,MAAM,EAAE,KAAK,GAAG;AAC9B,UAAI,CAAC,MAAM,KAAK,GAAG;AACjB,gBAAQ,OAAO,MAAM,8EAAoE;AACzF,eAAO;AAAA,MACT;AACA,YAAM,aAAyB,CAAC,oBAAoB,CAAC;AAErD,YAAM,MAAM,OAAO,QAAgB,SAA6B;AAC9D,cAAM,SAAS,SAAS,MAAM;AAC9B,YAAI,OAAQ,SAAQ,OAAO,MAAM,SAAS,QAAQG,UAAS,MAAM,CAAC,KAAK,MAAM,QAAQ,MAAM,UAAU,WAAW,IAAI,KAAK,GAAG;AAAA,CAAoF;AAChN,YAAI;AACF,qBAAW,KAAK,MAAM,SAAS,QAAQ,IAAI,CAAC;AAAA,QAC9C,SAAS,OAAO;AACd,gBAAM,CAAC,OAAO,GAAG,IAAI,IAAI,OAAO,iBAAiB,QAAQ,MAAM,UAAU,KAAK,EAAE,MAAM,IAAI;AAC1F,kBAAQ,OAAO,MAAM,iBAAiB,KAAK,GAAG,KAAK,SAAS;AAAA,EAAK,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI;AAAA,cAAY,KAAK,SAAS,CAAC,6CAAwC,EAAE,KAAK,EAAE;AAAA,CAAI;AAAA,QACnM;AAAA,MACF;AAGA,YAAM,OAAO,CAAC,aAAaF,YAAWF,SAAQ,MAAM,cAAc,CAAC,IAAK,WAAW,IAAI,EAAE,QAAQI,UAAS,IAAI,IAAK;AACnH,UAAI,KAAM,OAAM,IAAI,MAAM,MAAM,MAAM,CAAC;AACvC,UAAI,CAAC,OAAO,MAAM,EAAE,UAAU,CAAC,KAAK,MAAM,GAAG;AAC3C,mBAAW,WAAW,aAAa,YAAY,IAAI,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,EAAG,OAAM,IAAI,QAAQ,QAAQ,QAAQ,IAAI;AAAA,MAC7H;AACA,iBAAW,QAAQ,OAAO,MAAM,GAAG;AACjC,cAAM,CAAC,MAAM,MAAM,IAAI,KAAK,SAAS,GAAG,IAAI,CAAC,KAAK,MAAM,GAAG,KAAK,QAAQ,GAAG,CAAC,GAAG,KAAK,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,QAAW,IAAI;AACpI,cAAM,KAAKJ,SAAQ,MAAM,MAAM;AAC/B,YAAI,CAACE,YAAWF,SAAQ,IAAI,cAAc,CAAC,GAAG;AAC5C,kBAAQ,OAAO,MAAM,iBAAiB,QAAQI,UAAS,EAAE,CAAC,sBAAiB,EAAE;AAAA,CAAI;AACjF;AAAA,QACF;AACA,cAAM,IAAI,IAAI,IAAI;AAAA,MACpB;AACA,YAAM,QAAQ,OAAO,MAAM,OAAO,KAAK,CAAC;AACxC,YAAM,WAAW,iBAAiB,YAAY,OAAO,EAAE,OAAO,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ,EAAE,CAAC;AAC/G,UAAI,KAAK,MAAM,GAAG;AAChB,gBAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,SAAS,IAAI,CAAC,YAAY,QAAQ,KAAK,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAC7F,eAAO;AAAA,MACT;AACA,cAAQ,OAAO,MAAM,GAAG,eAAe,YAAY,UAAU,KAAK,CAAC;AAAA,CAAI;AAGvE,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,SAAS;AACZ,YAAM,MAAMJ,SAAQ,MAAM,MAAM,KAAK,KAAK,eAAe;AACzD,UAAI,WAAW;AACb,cAAM,EAAE,eAAe,mBAAAK,mBAAkB,IAAI,MAAM,eAAe;AAClE,cAAMC,YAAW,cAAc,EAAE,KAAK,CAAC;AACvC,cAAM,WAAW,iBAAiBA,SAAQ;AAC1C,YAAI,SAAS,QAAQ;AACnB,kBAAQ,OAAO,MAAM;AAAA,IAAsE,SAAS,KAAK,MAAM,CAAC;AAAA,CAAI;AACpH,iBAAO;AAAA,QACT;AACA,sBAAc,KAAKD,mBAAkBC,SAAQ,GAAG,MAAM;AACtD,gBAAQ,OAAO,MAAM,GAAG,GAAG,KAAKA,UAAS,QAAQ,MAAM,iBAAiBA,UAAS,UAAU,OAAO;AAAA,CAAY;AAC9G,eAAO;AAAA,MACT;AACA,YAAM,EAAE,kBAAkB,IAAI,MAAM,eAAe;AACnD,YAAM,WAAW,MAAM,SAAS,MAAM,MAAM,MAAM,CAAC;AACnD,oBAAc,KAAK,kBAAkB,QAAQ,GAAG,MAAM;AACtD,cAAQ,OAAO,MAAM,GAAG,GAAG,KAAK,QAAQ,QAAQ,CAAC;AAAA,CAAI;AACrD,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,SAAS;AACZ,UAAI,CAAC,WAAW;AACd,cAAM,WAAW,MAAM,SAAS,MAAM,MAAM,MAAM,CAAC;AACnD,gBAAQ,OAAO,MAAM,GAAG,QAAQ,QAAQ,CAAC;AAAA,CAAI;AAG7C,cAAM,MAAM,oBAAoB;AAChC,cAAM,QAAQ,cAAc,UAAU,GAAG;AACzC,cAAM,SAAS,iBAAiB,UAAU,MAAM,KAAK;AACrD,YAAI,OAAO,QAAQ;AACjB,kBAAQ,OAAO,MAAM,GAAG,OAAO,WAAW,IAAI,kBAAkB,GAAG,OAAO,MAAM,cAAc;AAAA,IAAgC,OAAO,KAAK,MAAM,CAAC;AAAA,CAAI;AAAA,QACvJ,OAAO;AACL,gBAAM,OAAO,SAAS,QAAQ,OAAO,CAAC,UAAW,eAAqC,SAAS,MAAM,KAAK,SAAS,EAAE,CAAC,EAAE;AACxH,kBAAQ,OAAO,MAAM,oBAAoB,IAAI;AAAA,CAA6D;AAAA,QAC5G;AACA,cAAMC,UAAS,WAAW,UAAU,IAAI;AACxC,cAAMC,SAAQ,MAAM,WAAW,UAAU,IAAI;AAC7C,cAAMC,UAAS,YAAY;AAC3B,cAAMC,UAAS,cAAc,MAAM,UAAUH,QAAO,QAAQ;AAG5D,cAAM,QAAQ,aAAa,UAAU,MAAM;AAAA,UACzC;AAAA,UACA,GAAIA,QAAO,SAAS,EAAE,QAAQA,QAAO,OAAO,IAAI,CAAC;AAAA,UACjD,YAAY,IAAI,QAAQ,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,OAAqB,QAAQ,EAAE,CAAC,CAAC;AAAA,QACxG,CAAC;AACD,YAAI,MAAM,OAAQ,SAAQ,OAAO,MAAM,gBAAgB,MAAM,WAAW,IAAI,mBAAmB,GAAG,MAAM,MAAM,aAAa;AAAA,IAAgB,MAAM,KAAK,MAAM,CAAC;AAAA,CAAI;AAAA,YAC5J,SAAQ,OAAO,MAAM,wDAAwD;AAClF,eAAO,OAAO,UAAU,MAAM,UAAU,CAACA,QAAO,MAAM,CAACC,UAAS,CAACC,WAAU,CAACC,UAAS,IAAI;AAAA,MAC3F;AACA,YAAM,EAAE,eAAe,kBAAkB,IAAI,MAAM,eAAe;AAClE,YAAM,OAAO,oBAAoB;AACjC,YAAM,QAAQ,cAAc,EAAE,KAAK,CAAC;AACpC,YAAM,WAAW,iBAAiB,KAAK;AACvC,UAAI,SAAS,QAAQ;AACnB,gBAAQ,OAAO,MAAM;AAAA,IAAkD,SAAS,KAAK,MAAM,CAAC;AAAA,CAAI;AAChG,eAAO;AAAA,MACT;AACA,UAAI,CAACR,YAAW,IAAI,GAAG;AACrB,gBAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAwC;AACpE,eAAO;AAAA,MACT;AAGA,UAAID,cAAa,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,kBAAkB,KAAK,GAAG;AAClF,gBAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAiF;AAC7G,eAAO;AAAA,MACT;AACA,cAAQ,OAAO,MAAM,GAAG,IAAI,cAAc,MAAM,QAAQ,MAAM,iBAAiB,MAAM,UAAU,OAAO;AAAA,CAAY;AAClH,YAAM,SAAS,WAAW,OAAO,IAAI;AACrC,YAAM,QAAQ,MAAM,WAAW,OAAO,MAAM,CAAC,WAAW,CAAC;AACzD,YAAM,SAAS,YAAY;AAE3B,YAAM,SAAS,cAAc,MAAM,QAAW,OAAO,QAAQ;AAC7D,aAAO,OAAO,MAAM,SAAS,UAAU,SAAS,IAAI;AAAA,IACtD;AAAA,IAEA,KAAK,SAAS;AACZ,YAAM,MAAM,MAAM,KAAK;AACvB,YAAM,SAAS,MAAMD,SAAQ,GAAG,IAAI,aAAa,IAAI;AAGrD,UAAIW,UAAS,QAAQ,YAAY,CAAC,EAAE,WAAW,IAAI,GAAG;AACpD,gBAAQ,OAAO,MAAM,GAAG,MAAM,kBAAkB,YAAY,CAAC;AAAA,CAAqD;AAClH,eAAO;AAAA,MACT;AACA,YAAM,OAAO,WAAW,MAAM;AAC9B,gBAAUC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,oBAAc,MAAM,WAAW,QAAQ,YAAY,CAAC,GAAG,MAAM;AAC7D,cAAQ,OAAO,MAAM,GAAG,IAAI,iCAAiC,YAAY,CAAC;AAAA,CAAI;AAC9E,YAAM,WAAW,mBAAmB,MAAM;AAC1C,UAAI,UAAU;AACZ,sBAAc,aAAa,MAAM,GAAG,UAAU,MAAM;AACpD,gBAAQ,OAAO,MAAM,GAAG,aAAa,MAAM,CAAC;AAAA,CAAyC;AAAA,MACvF;AACA,aAAO;AAAA,IACT;AAAA,IAEA;AACE,cAAQ,OAAO,MAAM,4CAA4C;AACjE,aAAO;AAAA,EACX;AACF;AAEA,IAAI;AACF,UAAQ,WAAW,MAAM,KAAK;AAChC,SAAS,OAAO;AACd,UAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAClF,UAAQ,WAAW;AACrB;",
6
6
  "names": ["existsSync", "readFileSync", "basename", "dirname", "join", "relative", "resolve", "main", "existsSync", "readFileSync", "dirname", "join", "resolve", "packageRoot", "existsSync", "readdirSync", "readFileSync", "join", "root", "resolve", "readFileSync", "existsSync", "join", "basename", "serializeRegistry", "registry", "linked", "shown", "loaded", "mapped", "relative", "dirname"]
7
7
  }
@@ -19,6 +19,13 @@ export declare function hasSeenIn(source: string): boolean;
19
19
  * it builds an id.
20
20
  */
21
21
  export declare function linkProblems(registry: Registry, ids: ReadonlySet<string>): string[];
22
+ /**
23
+ * The parts an app's page may name under When and When not: the package's and
24
+ * the app's own (B8). The one copy: `estiva-ui check` and `gates:status` both
25
+ * read it, so they cannot disagree. Status once knew the app's parts only, and
26
+ * read six of Peek's pages as broken that CI passed (UIG-26).
27
+ */
28
+ export declare function nameableParts(app: Registry, pkg: Registry): Set<string>;
22
29
  /**
23
30
  * Every way an app's catalogue breaks the contract, one line each, naming the
24
31
  * part, its file and what to do. Empty when it keeps it.
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/registry/contract.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,2DAA2D;AAC3D,eAAO,MAAM,aAAa,sDAAuD,CAAA;AAEjF,iDAAiD;AACjD,eAAO,MAAM,cAAc,4CAA6C,CAAA;AAKxE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBzD;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CASnF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,WAAW,CAAC,MAAM,CAAgD,GAAG,MAAM,EAAE,CA4BtJ;AASD,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpD;AAED,mFAAmF;AACnF,eAAO,MAAM,cAAc,QAAsB,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAMhH;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAe,EAAE,EAAE;IAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAsBhL"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/registry/contract.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,2DAA2D;AAC3D,eAAO,MAAM,aAAa,sDAAuD,CAAA;AAEjF,iDAAiD;AACjD,eAAO,MAAM,cAAc,4CAA6C,CAAA;AAKxE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBzD;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CASnF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,WAAW,CAAC,MAAM,CAAgD,GAAG,MAAM,EAAE,CA4BtJ;AASD,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpD;AAED,mFAAmF;AACnF,eAAO,MAAM,cAAc,QAAsB,CAAA;AAEjD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAMhH;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAe,EAAE,EAAE;IAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAsBhL"}
@@ -12,7 +12,7 @@
12
12
  */
13
13
  export { buildRegistry, readIndexExports, serializeRegistry, type BuildOptions } from './build';
14
14
  export { buildAppRegistry, type AppBuildOptions } from './app';
15
- export { CONTRACT_KINDS, NO_ALTERNATIVE, PAGE_SECTIONS, contractProblems, hasSeenIn, linkProblems, namedParts, pageProblem, whenNotProblem } from './contract';
15
+ export { CONTRACT_KINDS, NO_ALTERNATIVE, PAGE_SECTIONS, contractProblems, hasSeenIn, linkProblems, nameableParts, namedParts, pageProblem, whenNotProblem } from './contract';
16
16
  export { docsLink, findInRegistries, findInRegistry, formatFindings, type Finding } from './find';
17
17
  export { CLASSES, SCHEMA_VERSION, validateRegistry, type AppFacts, type EntryBehaviour, type EntryClass, type FileWithoutPart, type EntryKind, type EntryStatus, type EntryVariant, type PurposeSource, type Registry, type RegistryEntry, type RegistryExclusion, } from './schema';
18
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/F,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,OAAO,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC9J,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAA;AACjG,OAAO,EACL,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/F,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,OAAO,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC7K,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAA;AACjG,OAAO,EACL,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAA"}
@@ -10,10 +10,11 @@ import {
10
10
  formatFindings,
11
11
  hasSeenIn,
12
12
  linkProblems,
13
+ nameableParts,
13
14
  namedParts,
14
15
  pageProblem,
15
16
  whenNotProblem
16
- } from "./chunk-XK747STJ.js";
17
+ } from "./chunk-4QWD3EBY.js";
17
18
  import {
18
19
  buildAppRegistry
19
20
  } from "./chunk-TQ6UQER3.js";
@@ -42,6 +43,7 @@ export {
42
43
  formatFindings,
43
44
  hasSeenIn,
44
45
  linkProblems,
46
+ nameableParts,
45
47
  namedParts,
46
48
  pageProblem,
47
49
  readIndexExports,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estiva-app/ui",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/registry.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "kind": "package",
5
5
  "repo": "estiva-ui",
6
6
  "package": "@estiva-app/ui",
7
- "packageVersion": "0.46.0",
7
+ "packageVersion": "0.47.0",
8
8
  "exports": 100,
9
9
  "typeExports": 109,
10
10
  "files": null
@@ -39,20 +39,22 @@ and when not to. Most of what gets asked for already exists somewhere.
39
39
  ## Gotchas
40
40
 
41
41
  The mistakes made here more than once, from the record (UIG-20 counted 542
42
- recorded defects; these 24 cover the 111 that are screen work). Each says what
43
- to do instead. Apps differ in frame and theme: never carry one app's layout,
44
- sizes or colours into another; take them from tokens.
42
+ recorded defects to 23 September, UIG-26 counted 647 to 30 September; these 24
43
+ cover the 113 that are screen work). Each says what to do instead. Apps differ
44
+ in frame and theme: never carry one app's layout, sizes or colours into
45
+ another; take them from tokens.
45
46
 
46
47
  **Layout**
47
48
 
48
- 1. **A header row drawn by hand** at the top of a column → `ContainerHeader`.
49
- Its actions are `IconButton`s with tooltips. A whole list column is
50
- `ListColumn`, which draws the bar; a field that adds to the list goes in its
51
- `above`.
49
+ 1. **A header row drawn by hand** at the top of a column, or `ContainerHeader`
50
+ over a box of your own → `Panel`, which draws `ContainerHeader`'s bar over a
51
+ body that scrolls. Its `actions` are `IconButton`s with tooltips. A whole list
52
+ column is `ListColumn`, which draws the bar; a field that adds to the list
53
+ goes in its `above`.
52
54
  2. **A list with no scroll container**, cut off at the fold (no lint sees a box
53
55
  that was never built) → a list column is `ListColumn`, which scrolls its
54
- rows; anything else that grows goes in a `ScrollArea`. Check with more rows
55
- than fit.
56
+ rows; a panel is `Panel`, whose body scrolls; anything else that grows goes
57
+ in a `ScrollArea`. Check with more rows than fit.
56
58
  3. **`EmptyState` at the wrong level** → `scope="page"` only when the whole
57
59
  page is empty, `scope="section"` for one part of it, placed inside the box
58
60
  its rows live in. Never wrap it in a box of your own that places it.
@@ -113,8 +115,9 @@ sizes or colours into another; take them from tokens.
113
115
  twice) → search for the thing first. Write the escape on the line above
114
116
  the statement: a gate escape never inside `eslint-disable`, a token-lint
115
117
  one inside `eslint-disable-next-line <rule> --`.
116
- 20. **Classes the lint cannot see**: in a const not named `…_CLASSES` or
117
- `…Styles`, or in an array that is `.join()`ed → keep classes in
118
+ 20. **Classes the lint cannot see**: in a const or map not named `…_CLASSES`
119
+ or `…Styles`, or in a list worked out while the code runs (a list of
120
+ written strings joined with `.join(' ')` is read) → keep classes in
118
121
  `className` or `cn()`, or in a map with one of those names.
119
122
  21. **The editor gate and this skill need a session started at the repository's
120
123
  top folder.** Started elsewhere, run the repository's `lint:rules` yourself
@@ -56,6 +56,7 @@ export const APP_TICKET_TITLES: Record<string, string> = {
56
56
  'UIG-28': 'Close the two holes in the token contract — arbitrary values, and inline style',
57
57
  'UIG-30': 'RichText — one component that draws a message\'s text, for both apps',
58
58
  'UIG-32': 'Peek and Ship take their gate pieces from the package',
59
+ 'UIG-33': 'Link — a whole row that is one link',
59
60
  'UIG-37': 'The gates refuse a TypeScript eslint-disable comment — know the rule names, rules off',
60
61
  'UIG-42': "What the gate can't see yet — a box around a part, a screen drawn by hand, two blind spots in the token check",
61
62
  }
@@ -4,6 +4,7 @@ import { join } from 'node:path'
4
4
  import { pathToFileURL } from 'node:url'
5
5
  import { ESLint, type Linter } from 'eslint'
6
6
  import { afterAll, describe, expect, it } from 'vitest'
7
+ import { appChecks } from './app-checks'
7
8
  import { countCommitted, hookRuns, skippedFolders, skipsListed } from './ci'
8
9
  import { writeGateCount } from './count'
9
10
  import { gateConfig, gateLint } from './gate-config'
@@ -425,4 +426,25 @@ describe('gates:status', () => {
425
426
  expect(out).toContain('Tickets shipish owns (1):')
426
427
  expect(out).toContain('✅ UIG-4')
427
428
  })
429
+
430
+ // UIG-26, Katerina's ruling of 30 September: eight open tickets moved to the next phase, and the close runs.
431
+ it('shows a ticket moved to the next phase, still checks it, and the aggregate does not wait for it', async () => {
432
+ const checks = (later: string) => `export default (h) => ({ repo: 'estiva-ui', siblings: [],\n all: [\n { ref: 'UIG-1', owner: 'estiva-ui', title: 'Count' },\n { ref: 'UIG-26', owner: 'estiva-ui', aggregate: true, title: 'Close' },\n { ref: 'UIG-39', owner: 'estiva-ui', title: 'Later'${later} },\n ],\n tickets: [\n { ref: 'UIG-1', owner: true, checks: [{ what: 'counted', run: () => h.PASS('yes') }] },\n { ref: 'UIG-26', owner: true, checks: [] },\n { ref: 'UIG-39', owner: true, checks: [{ what: 'written', run: () => h.FAIL('not yet') }] },\n ] })\n`
433
+ const moved = await runStatus({ root: app('status-later', { 'scripts/gates-checks.mjs': checks(", later: 'Katerina, 30 September: the next phase'") }), detail: true })
434
+ expect(moved).toContain('⏭ UIG-39')
435
+ expect(moved).toContain('Katerina, 30 September: the next phase')
436
+ expect(moved).toContain('✗ estiva-ui written — not yet')
437
+ expect(moved).toContain('✅ UIG-26')
438
+ expect(moved).toContain('1 of 1 done; 1 moved to the next phase')
439
+ expect(moved).toContain('⏭ 1 next phase')
440
+ const waiting = await runStatus({ root: app('status-waiting', { 'scripts/gates-checks.mjs': checks('') }) })
441
+ expect(waiting).toContain('⬜ UIG-26')
442
+ expect(waiting).not.toContain('next phase')
443
+ })
444
+
445
+ // A made app's UIG-33 row printed with no title (UIG-26): every row the package checks in an app has one.
446
+ it('gives every ticket an app checks a title', () => {
447
+ const tickets = appChecks(helpers(app('titles', {})), { page: 'src/pages/HomePage.tsx' })
448
+ expect(tickets.filter((t) => !t.title).map((t) => t.ref)).toEqual([])
449
+ })
428
450
  })
@@ -101,6 +101,8 @@ export interface TicketListEntry {
101
101
  title: string
102
102
  parts?: string[]
103
103
  aggregate?: boolean
104
+ /** Moved to the next phase, and by whose ruling: still checked, shown ⏭, and not waited for by the aggregate. */
105
+ later?: string
104
106
  }
105
107
  export interface GateSpec {
106
108
  repo: string
@@ -119,6 +121,7 @@ interface Row {
119
121
  checks: (CheckResult & { repo: string; what: string })[]
120
122
  status?: string
121
123
  aggregate?: boolean
124
+ later?: string
122
125
  }
123
126
  interface Report {
124
127
  schema: 1
@@ -199,9 +202,11 @@ export function helpers(ROOT: string): GateHelpers {
199
202
 
200
203
  async contract(appDir) {
201
204
  try {
202
- const { buildAppRegistry, contractProblems, CONTRACT_KINDS } = await import('../registry/index')
205
+ const { buildAppRegistry, contractProblems, nameableParts, CONTRACT_KINDS } = await import('../registry/index')
203
206
  const registry = buildAppRegistry({ root: abs(appDir) })
204
- const problems = contractProblems(registry, abs(appDir))
207
+ // The package's catalogue, beside its package.json: a page may name its parts too, as in CI.
208
+ const pkg = createRequire(import.meta.url)('../../registry.json') as Parameters<typeof nameableParts>[1]
209
+ const problems = contractProblems(registry, abs(appDir), nameableParts(registry, pkg))
205
210
  const owed = registry.entries.filter((entry) => (CONTRACT_KINDS as readonly string[]).includes(entry.app?.class ?? '')).length
206
211
  if (problems.length) return FAIL(`${problems.length} of ${owed}; the first: ${problems[0]}`)
207
212
  return PASS(`all ${owed} reusable parts keep the usage-page contract and are drawn somewhere`)
@@ -445,7 +450,7 @@ export function helpers(ROOT: string): GateHelpers {
445
450
 
446
451
  // ── running the checks ─────────────────────────────────────────────────────
447
452
 
448
- const STATUS: Record<string, string> = { done: '✅', started: '🚧', none: '⬜', unknown: '❔' }
453
+ const STATUS: Record<string, string> = { done: '✅', started: '🚧', none: '⬜', unknown: '❔', later: '⏭' }
449
454
 
450
455
  function statusOf(checks: CheckResult[]) {
451
456
  const n = (r: CheckResult['result']) => checks.filter((c) => c.result === r).length
@@ -553,6 +558,7 @@ function printRows(rows: Row[], { showOwner, detail }: { showOwner: boolean; det
553
558
  const pass = r.checks.filter((c) => c.result === 'pass').length
554
559
  const count = r.checks.length ? `${pass} of ${r.checks.length}` : 'no checks here'
555
560
  lines.push(`${STATUS[r.status ?? 'unknown']} ${pad(r.ref, 7)} ${pad(r.title ?? '', width)} ${showOwner ? pad(r.ownerRepo ?? '', 10) : ''}${count}`)
561
+ if (r.later) lines.push(` ${r.later}`)
556
562
  if (detail || r.status === 'started' || r.status === 'unknown') {
557
563
  for (const c of r.checks) {
558
564
  const mark = { pass: '✓', part: '½', fail: '✗', unknown: '?' }[c.result]
@@ -565,7 +571,8 @@ function printRows(rows: Row[], { showOwner, detail }: { showOwner: boolean; det
565
571
 
566
572
  function summary(rows: Row[]) {
567
573
  const n = (s: string) => rows.filter((r) => r.status === s).length
568
- return `${STATUS.done} ${n('done')} done · ${STATUS.started} ${n('started')} started · ${STATUS.none} ${n('none')} not started · ${STATUS.unknown} ${n('unknown')} could not check`
574
+ const later = n('later') ? ` · ${STATUS.later} ${n('later')} next phase` : ''
575
+ return `${STATUS.done} ${n('done')} done · ${STATUS.started} ${n('started')} started · ${STATUS.none} ${n('none')} not started · ${STATUS.unknown} ${n('unknown')} could not check${later}`
569
576
  }
570
577
 
571
578
  export interface StatusOptions {
@@ -667,13 +674,17 @@ export async function runStatus({ root = process.cwd(), app = '.', json = false,
667
674
  for (const f of found.filter((f) => f.missing)) {
668
675
  if ((t.parts ?? []).includes(f.name) || t.owner === f.name) checks.push({ repo: f.name, what: `${f.name}'s part`, result: 'unknown', detail: f.note })
669
676
  }
670
- return { ref: t.ref, title: t.title, ownerRepo: t.owner, checks, aggregate: t.aggregate }
677
+ return { ref: t.ref, title: t.title, ownerRepo: t.owner, checks, aggregate: t.aggregate, later: t.later }
671
678
  })
672
- for (const row of rows) row.status = statusOf(row.checks)
679
+ // A ticket moved to the next phase is still checked (--detail shows how far it is), but it
680
+ // reads ⏭ whatever its checks say, and the aggregate does not wait for it.
681
+ for (const row of rows) row.status = row.later ? 'later' : statusOf(row.checks)
673
682
  for (const row of rows.filter((r) => r.aggregate)) {
674
- const others = rows.filter((r) => r !== row)
683
+ const others = rows.filter((r) => r !== row && !r.later)
675
684
  const done = others.filter((r) => r.status === 'done').length
676
- row.checks.push({ repo: 'all', what: 'every other ticket is done', ...(done === others.length ? PASS(`${done} of ${others.length} done`) : FAIL(`${done} of ${others.length} done`)) })
685
+ const moved = rows.filter((r) => r.later).length
686
+ const said = `${done} of ${others.length} done${moved ? `; ${moved} moved to the next phase` : ''}`
687
+ row.checks.push({ repo: 'all', what: 'every other ticket of this phase is done', ...(done === others.length ? PASS(said) : FAIL(said)) })
677
688
  row.status = statusOf(row.checks)
678
689
  }
679
690
 
@@ -36,7 +36,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from
36
36
  import { tmpdir } from 'node:os'
37
37
  import { basename, dirname, join, relative, resolve } from 'node:path'
38
38
  import { fileURLToPath } from 'node:url'
39
- import { CONTRACT_KINDS, contractProblems, linkProblems, nameProblems } from './contract'
39
+ import { CONTRACT_KINDS, contractProblems, linkProblems, nameableParts, nameProblems } from './contract'
40
40
  import { findInRegistries, formatFindings } from './find'
41
41
  import { behindBy, findSiblings, workspaceOf } from './siblings'
42
42
  import { loaderPath, loaderProblem, loaderText, repositoryOf, settingsPath, settingsWithSearch } from './skill'
@@ -280,7 +280,7 @@ async function main(): Promise<number> {
280
280
  // The usage-page contract and the story links (UIG-19): the one copy, run by every app's `gate` job.
281
281
  // A When not may name a part of the package or of this app (B8).
282
282
  const pkg = readPackageRegistry()
283
- const known = new Set([...pkg.entries, ...registry.entries].map((e) => e.name))
283
+ const known = nameableParts(registry, pkg)
284
284
  const broken = contractProblems(registry, root, known)
285
285
  if (broken.length) {
286
286
  process.stderr.write(`${broken.length === 1 ? 'a part breaks' : `${broken.length} parts break`} the usage-page contract:\n ${broken.join('\n ')}\n`)
@@ -13,12 +13,14 @@
13
13
  * used to call drawn nowhere;
14
14
  * - a Windows checkout, read with CRLF.
15
15
  */
16
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
16
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
17
17
  import { tmpdir } from 'node:os'
18
18
  import { dirname, join } from 'node:path'
19
19
  import { afterAll, describe, expect, it } from 'vitest'
20
20
  import { buildAppRegistry } from './app'
21
- import { contractProblems, hasSeenIn, linkProblems, nameProblems, pageProblem, whenNotProblem } from './contract'
21
+ import { helpers } from '../gates/status'
22
+ import { contractProblems, hasSeenIn, linkProblems, nameableParts, nameProblems, pageProblem, whenNotProblem } from './contract'
23
+ import type { Registry } from './schema'
22
24
 
23
25
  const made: string[] = []
24
26
  afterAll(() => {
@@ -128,6 +130,20 @@ describe('an app against the contract', () => {
128
130
  expect(contractProblems(buildAppRegistry({ root: dir, repo: 'fixture' }), dir)).toEqual([])
129
131
  })
130
132
 
133
+ // UIG-26: gates:status knew only the app's own parts, and read six of Peek's pages as broken that CI passed.
134
+ it('lets a page name a package part, in estiva-ui check and in gates:status alike', async () => {
135
+ const dir = app({
136
+ 'src/Badge.tsx': part('Badge'),
137
+ 'src/Badge.mdx': PAGE.replace('No alternative: a count has only this badge.', 'A framed column is `Panel`.'),
138
+ 'src/Badge.stories.tsx': "import { Badge } from './Badge'\nconst meta = { title: 'Parts/Badge', component: Badge }\nexport default meta\nexport const Default = {}\n",
139
+ })
140
+ const registry = buildAppRegistry({ root: dir, repo: 'fixture' })
141
+ expect(contractProblems(registry, dir)).toEqual([expect.stringContaining('"When not" names no other part')])
142
+ const pkg = JSON.parse(readFileSync(join(process.cwd(), 'registry.json'), 'utf8')) as Registry
143
+ expect(contractProblems(registry, dir, nameableParts(registry, pkg))).toEqual([])
144
+ expect(await helpers(dir).contract('.')).toMatchObject({ result: 'pass' })
145
+ })
146
+
131
147
  it('names a reusable part with no page, and the file to write', () => {
132
148
  const dir = app({ 'src/Badge.tsx': part('Badge') })
133
149
  expect(contractProblems(buildAppRegistry({ root: dir, repo: 'fixture' }), dir)).toEqual([