@estiva-app/ui 0.46.0 → 0.48.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.
Files changed (42) hide show
  1. package/dist/BlockNodes.d.ts +73 -0
  2. package/dist/BlockNodes.d.ts.map +1 -0
  3. package/dist/editor.d.ts +1 -0
  4. package/dist/editor.d.ts.map +1 -1
  5. package/dist/editor.js +141 -0
  6. package/dist/editor.js.map +4 -4
  7. package/dist/gates/app-checks.d.ts.map +1 -1
  8. package/dist/gates/{chunk-P5W6O5TF.js → chunk-BS5JASHV.js} +14 -9
  9. package/dist/gates/{chunk-P5W6O5TF.js.map → chunk-BS5JASHV.js.map} +3 -3
  10. package/dist/gates/cli.js +1 -1
  11. package/dist/gates/index.js +2 -1
  12. package/dist/gates/index.js.map +2 -2
  13. package/dist/gates/status.d.ts +2 -0
  14. package/dist/gates/status.d.ts.map +1 -1
  15. package/dist/registry/{chunk-XK747STJ.js → chunk-4QWD3EBY.js} +5 -1
  16. package/dist/registry/chunk-4QWD3EBY.js.map +7 -0
  17. package/dist/registry/cli.js +4 -3
  18. package/dist/registry/cli.js.map +2 -2
  19. package/dist/registry/contract.d.ts +7 -0
  20. package/dist/registry/contract.d.ts.map +1 -1
  21. package/dist/registry/index.d.ts +1 -1
  22. package/dist/registry/index.d.ts.map +1 -1
  23. package/dist/registry/index.js +3 -1
  24. package/package.json +1 -1
  25. package/registry.json +79 -3
  26. package/skill/estiva-ui.md +14 -11
  27. package/src/BlockNodes.mdx +82 -0
  28. package/src/BlockNodes.stories.tsx +79 -0
  29. package/src/BlockNodes.test.tsx +117 -0
  30. package/src/BlockNodes.tsx +242 -0
  31. package/src/editor.ts +10 -0
  32. package/src/gates/app-checks.ts +1 -0
  33. package/src/gates/gates.test.ts +22 -0
  34. package/src/gates/status.ts +19 -8
  35. package/src/registry/cli.ts +2 -2
  36. package/src/registry/contract.test.ts +18 -2
  37. package/src/registry/contract.ts +10 -0
  38. package/src/registry/index.ts +1 -1
  39. package/src/registry/registry.test.ts +10 -8
  40. package/src/registry/skill.test.ts +2 -0
  41. package/stories/Choosing.mdx +1 -0
  42. package/dist/registry/chunk-XK747STJ.js.map +0 -7
@@ -25,6 +25,8 @@ import { SCHEMA_VERSION, validateRegistry, type Registry, type RegistryEntry } f
25
25
  */
26
26
  const root = process.cwd()
27
27
  const registry = buildRegistry({ root })
28
+ /** Editor extensions: named like components by the catalogue, and not React components. */
29
+ const EDITOR_EXTENSIONS = ['KeptSelection', 'BlockId', 'UnknownBlock', 'ReferenceNode', 'AttachmentNode']
28
30
  const entry = (name: string): RegistryEntry => {
29
31
  const found = registry.entries.find((candidate) => candidate.name === name)
30
32
  if (!found) throw new Error(`no entry for ${name}`)
@@ -46,19 +48,19 @@ describe('the registry builds', () => {
46
48
  expect(registry.entries.map((one) => one.name).sort()).toEqual(values.map((one) => one.name).sort())
47
49
  })
48
50
 
49
- it('counts 100 names over 63 files: 86 components, 13 helpers and 1 hook (the editor parts joined on 29 September, UIG-31)', () => {
51
+ it('counts 104 names over 64 files: 90 components, 13 helpers and 1 hook (the block nodes joined on 30 September, MAN-9)', () => {
50
52
  // The reconciliation GATES.md §24 explains. If this changes, that changes.
51
- // KeptSelection counts as a component by its name; it is an editor extension,
52
- // documented on SelectionToolbar's page.
53
+ // The five `EDITOR_EXTENSIONS` count as components by their names; they are
54
+ // editor extensions, documented on SelectionToolbar's and BlockNodes' pages.
53
55
  const kinds = registry.entries.reduce<Record<string, number>>((all, one) => ({ ...all, [one.kind]: (all[one.kind] ?? 0) + 1 }), {})
54
- expect(kinds).toEqual({ component: 86, helper: 13, hook: 1 })
55
- expect(new Set(registry.entries.map((one) => one.sourceFile)).size).toBe(63)
56
+ expect(kinds).toEqual({ component: 90, helper: 13, hook: 1 })
57
+ expect(new Set(registry.entries.map((one) => one.sourceFile)).size).toBe(64)
56
58
  })
57
59
 
58
60
  it('gives every entry a purpose, from its own page or from the comment above it', () => {
59
61
  expect(registry.entries.filter((one) => one.purpose.trim() === '')).toEqual([])
60
62
  expect(registry.entries.filter((one) => one.purposeFrom === 'page').length).toBe(61)
61
- expect(registry.entries.filter((one) => one.purposeFrom === 'comment').length).toBe(39)
63
+ expect(registry.entries.filter((one) => one.purposeFrom === 'comment').length).toBe(43)
62
64
  })
63
65
 
64
66
  it('takes a name with a page of its own from the page, and a name without one from the code', () => {
@@ -323,8 +325,8 @@ describe('a second parser agrees', async () => {
323
325
  }
324
326
 
325
327
  expect(differences).toEqual([])
326
- // KeptSelection is named like a component and is an editor extension: nothing for docgen to read.
327
- expect(seen).toBe(registry.entries.filter((one) => one.kind === 'component' && one.name !== 'KeptSelection').length)
328
+ // Named like components and are editor extensions: nothing for docgen to read.
329
+ expect(seen).toBe(registry.entries.filter((one) => one.kind === 'component' && !EDITOR_EXTENSIONS.includes(one.name)).length)
328
330
  })
329
331
  })
330
332
 
@@ -39,6 +39,7 @@ describe('the skill', () => {
39
39
  const props: [string, string][] = [
40
40
  ['EmptyState', 'scope'],
41
41
  ['ListColumn', 'above'],
42
+ ['Panel', 'actions'],
42
43
  ['IconButton', 'tooltip'],
43
44
  ['SectionHeader', 'hover'],
44
45
  ['WithTooltip', 'inline'],
@@ -52,6 +53,7 @@ describe('the skill', () => {
52
53
 
53
54
  it('carries the four regressions the ticket names', () => {
54
55
  expect(text).toMatch(/header row drawn by hand[\s\S]*?`ContainerHeader`/)
56
+ expect(text).toMatch(/header row drawn by hand[\s\S]*?`Panel`/) // an app's bare ContainerHeader is refused since 0.37.0
55
57
  expect(text).toMatch(/no scroll container[\s\S]*?`ListColumn`/)
56
58
  expect(text).toMatch(/`EditableText` rendering too large/)
57
59
  expect(text).toMatch(/`EmptyState` at the wrong level[\s\S]*?scope="page"/)
@@ -68,6 +68,7 @@ fork freely, owe nothing back, mention it on the package's ticket.
68
68
  | One window to find something and act on it, opened by a shortcut | **CommandPalette** — rows, forms and written answers, one level at a time |
69
69
  | A list that opens as you type in an editor — `/` for commands, `@` for people | **SuggestionMenu** — the editor keeps the caret and hands it the arrow keys; `suggestionPopup` from `@estiva-app/ui/editor` wires it to a Tiptap plugin |
70
70
  | Bold, italic and the like over selected text in an editor | **SelectionToolbar**, from `@estiva-app/ui/editor` — it follows the selection by itself |
71
+ | An editor that saves a block document and must keep its block ids | **BlockNodes** — `BlockId`, `UnknownBlock`, `ReferenceNode` and `AttachmentNode` from `@estiva-app/ui/editor`, beside StarterKit and TableKit |
71
72
  | Anything with a title, body and footer | **DialogShell** |
72
73
  | "Are you sure?" | **ConfirmDialog** |
73
74
  | A picture full screen, from a thumbnail | **Lightbox** — an **AttachmentCard** opens one by itself |
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/registry/contract.ts", "../../src/registry/find.ts"],
4
- "sourcesContent": ["/**\n * The usage-page contract, checked against an app's catalogue (UIG-19).\n *\n * Every part an app classifies **reusable** (or **promote-candidate**) has a\n * page beside it \u2014 `<PartName>.mdx` in the component's own folder \u2014 that opens\n * with a line saying what the part is, then `When`, `When not`, `How` (with\n * code) and `What it owns`, in that order. And it is drawn somewhere: a story\n * of its own, or a **Seen in** line on its page naming the stories that draw\n * it, or saying why none can (Katerina, 21 and 23 September 2026). A one-off\n * needs only its class and a purpose line, which the catalogue already refuses\n * to build without.\n *\n * This is the one copy. Peek and Ship each used to carry their own, pasted into\n * their `scripts/gates-checks.mjs` and run only by `gates:status`, so a page\n * could lose its `When not` and still merge. `estiva-ui check` runs this in\n * every app's `gate` job (docs/GATES.md \u00A723: one copy, in the package).\n *\n * The package's own pages keep the fuller contract in `src/pages.test.ts`,\n * which also reads `What it owns` back against the lint rule.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Registry } from './schema'\n\n/** The four headings, in the order a page carries them. */\nexport const PAGE_SECTIONS = ['When', 'When not', 'How', 'What it owns'] as const\n\n/** The classes that owe a page and a picture. */\nexport const CONTRACT_KINDS = ['reusable', 'promote-candidate'] as const\n\n/** The line that stands in for a story of the part's own. */\nconst SEEN_IN = /^\\*\\*Seen in\\*\\*/m\n\n/**\n * Why a page breaks the contract, or null when it keeps it.\n *\n * Read on normalised text: the apps are checked out with CRLF on Windows.\n */\nexport function pageProblem(source: string): string | null {\n const text = source.replace(/\\r\\n/g, '\\n')\n const heads = [...text.matchAll(/^##+\\s+(.+?)\\s*$/gm)].map((m) => m[1])\n const at = PAGE_SECTIONS.map((s) => heads.indexOf(s))\n const missing = PAGE_SECTIONS.filter((_, i) => at[i] < 0)\n if (missing.length) return `no ${missing.map((s) => `\"${s}\"`).join(', ')} section`\n if (at.some((i, n) => n > 0 && i < at[n - 1])) return `sections out of order: they go ${PAGE_SECTIONS.join(', ')}`\n // The opening line: the first paragraph under the title that is not markup.\n const opening = text\n .split(/^# .*$/m)[1]\n ?.split(/^##\\s/m)[0]\n ?.split(/\\n\\s*\\n/)\n .map((s) => s.trim())\n .find((s) => s && !s.startsWith('<') && !s.startsWith('import '))\n if (!opening) return 'no opening line under the title saying what the part is'\n // Only the How section: the code block must sit under How, not in a later section\n // (a page with none under How and one under What it owns passed, R18).\n const how = text.split(/^##\\s+How\\s*$/m)[1]?.split(/^##\\s/m)[0] ?? ''\n if (!/```tsx?\\n/.test(how)) return 'no code under \"How\"'\n return null\n}\n\n/** Whether a page records where the part is drawn, in place of a story of its own. */\nexport function hasSeenIn(source: string): boolean {\n return SEEN_IN.test(source.replace(/\\r\\n/g, '\\n'))\n}\n\n/**\n * Every catalogue link that names a story or a docs page its Storybook does not\n * have (UIG-19: a dead link is a build failure, not a dead end found six weeks\n * later). `ids` is every entry id in the Storybook's own index \u2014 what\n * `storybook index` writes \u2014 so this is Storybook's answer, not a guess at how\n * it builds an id.\n */\nexport function linkProblems(registry: Registry, ids: ReadonlySet<string>): string[] {\n const problems: string[] = []\n for (const entry of registry.entries) {\n for (const key of ['storyId', 'docsId'] as const) {\n const id = entry[key]\n if (id && !ids.has(id)) problems.push(`${entry.name} (${entry.sourceFile}) links to ${key === 'storyId' ? 'a story' : 'a docs page'} its Storybook does not have: ${id}`)\n }\n }\n return problems\n}\n\n/**\n * Every way an app's catalogue breaks the contract, one line each, naming the\n * part, its file and what to do. Empty when it keeps it.\n *\n * `root` is the app's own folder: the one its catalogue's paths are relative to.\n */\nexport function contractProblems(registry: Registry, root: string, known: ReadonlySet<string> = new Set(registry.entries.map((e) => e.name))): string[] {\n const problems: string[] = []\n for (const entry of registry.entries) {\n const cls = entry.app?.class\n if (!cls || !(CONTRACT_KINDS as readonly string[]).includes(cls)) continue\n // Where the page must be: beside the component, named after the part \u2014\n // the same place the catalogue looks (src/registry/app.ts).\n const page = join(entry.sourceFile.replace(/[^/\\\\]+$/, ''), `${entry.name}.mdx`).replace(/\\\\/g, '/')\n const where = `${entry.name} (${cls}, ${entry.sourceFile})`\n if (!existsSync(join(root, page))) {\n problems.push(`${where} has no usage page: write ${page}, with an opening line, then ${PAGE_SECTIONS.join(', ')}`)\n continue\n }\n const text = readFileSync(join(root, page), 'utf8')\n const problem = pageProblem(text)\n if (problem) problems.push(`${where}: ${page} has ${problem}`)\n const whenNot = whenNotProblem(text, [entry.name], known)\n if (whenNot) problems.push(`${where}: ${page}: ${whenNot}`)\n if (entry.storyId === null && !hasSeenIn(text)) {\n problems.push(`${where} is drawn nowhere: give it a story, or write a \"**Seen in**\" line on ${page} naming the stories that draw it, or why none can`)\n }\n // A reusable part links to its page or a story (Katerina's ruling B10, 25 September):\n // the catalogue reads the page's `<Meta title>`, so a page without one leaves it unlinked.\n if (entry.storyId === null && entry.docsId === null) {\n problems.push(`${where} links to nothing in Storybook: give ${page} a \\`<Meta title=\"\u2026\" />\\`, or give the part a story`)\n }\n }\n return problems\n}\n\n/** Storybook's id for a title: lower case, every run of other characters one hyphen. */\nconst toStoryId = (title: string) =>\n title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n\n/** The names a page's section writes as a part: bold or in backticks, starting with a capital. */\nexport function namedParts(section: string): string[] {\n return [...section.matchAll(/\\*\\*`?([A-Z][A-Za-z0-9]+)`?\\*\\*|`([A-Z][A-Za-z0-9]+)`/g)].map((m) => m[1] ?? m[2])\n}\n\n/** The line a \"When not\" starts where no part stands in (Katerina's ruling B8). */\nexport const NO_ALTERNATIVE = /^No alternative:/m\n\n/**\n * Why a page's `When not` breaks the ruling B8 (25 September), or null. The plan\n * asked for \"When not \u2014 naming the alternative\"; CI checked only that the\n * heading was there, and 29 pages across the three repos named nothing. Now the\n * section names a real part other than the page's own (`own`), from `known`, or\n * a line of it starts \"No alternative:\" and says why there is none.\n */\nexport function whenNotProblem(source: string, own: readonly string[], known: ReadonlySet<string>): string | null {\n const section = source.replace(/\\r\\n/g, '\\n').split(/^##\\s+When not\\s*$/m)[1]?.split(/^##\\s/m)[0]\n // A missing section is pageProblem's to report.\n if (section === undefined || NO_ALTERNATIVE.test(section)) return null\n if (namedParts(section).some((name) => known.has(name) && !own.includes(name))) return null\n return '\"When not\" names no other part: name the part to use instead, as **Name** or `Name`, or start a line \"No alternative:\" that says why there is none'\n}\n\n/**\n * What an app's usage pages name that is not there (the re-review after the audit\n * before UIG-26, R14): a part named under When or When not that no catalogue has \u2014\n * Peek's StartTopicDialog sent readers to CreateTopicDialog, deleted by FOL-23 \u2014\n * and a story a **Seen in** line names that no Storybook has. `known` is every part\n * of the package and the app; `titles` is the app's Storybook titles, when there\n * is an index to read them from; `packageIds` the package's story and docs ids, so\n * a page may send readers to the package's Storybook too.\n */\nexport function nameProblems(registry: Registry, root: string, { known, titles, packageIds = [] }: { known: Set<string>; titles?: Set<string>; packageIds?: string[] }): string[] {\n const problems: string[] = []\n for (const entry of registry.entries) {\n if (!entry.docPage || !existsSync(join(root, entry.docPage))) continue\n const text = readFileSync(join(root, entry.docPage), 'utf8').replace(/\\r\\n/g, '\\n')\n for (const heading of ['When', 'When not']) {\n const section = text.split(new RegExp(`^##\\\\s+${heading}\\\\s*$`, 'm'))[1]?.split(/^##\\s/m)[0] ?? ''\n for (const name of new Set(namedParts(section).filter((n) => !known.has(n)))) {\n problems.push(`${entry.docPage}: \"${heading}\" names ${name}, which is no part of the package or this app`)\n }\n }\n if (!titles) continue\n const seenIn = text.match(/^\\*\\*Seen in\\*\\*[^\\n]*(?:\\n(?!\\n)[^\\n]*)*/m)?.[0] ?? ''\n for (const [, raw] of seenIn.matchAll(/\\*([A-Z][^*/]*\\/[^*]+)\\*/g)) {\n // A title may wrap across lines, and a line may break after its slash.\n const title = raw.replace(/\\s+/g, ' ').replace(/\\s*\\/\\s*/g, '/').trim()\n const id = toStoryId(title)\n if (titles.has(title) || packageIds.some((p) => p.startsWith(`${id}--`))) continue\n problems.push(`${entry.docPage}: \"Seen in\" names ${title}, which no Storybook has`)\n }\n }\n return problems\n}\n", "/**\n * The catalogue's reader (UIG-12): `ui:find <words>`.\n *\n * It exists to prove the format. A schema nobody consumes is a schema nobody\n * has tested, and the questions it has to answer are regression tests, not the\n * scope: \"floating panel\" must find `Popover`, \"scrolling\" `ScrollArea`,\n * \"empty\" `EmptyState`.\n *\n * It is also the answer to \"what do we have for this?\", which is the question\n * the wall cannot answer: a lint rule refuses the wrong thing, and this is\n * where the right one is named (UIG-20 makes a session ask it first).\n */\nimport type { Registry, RegistryEntry } from './schema'\n\nexport interface Finding {\n entry: RegistryEntry\n /** How many of the query's words matched anywhere. Ranked on this first. */\n matched: number\n score: number\n /** Which fields matched, for the reader to say why. */\n where: string[]\n /**\n * The props the query named **by name**, so the answer shows those rather\n * than all of them. A prop whose note merely carries one of the words counts\n * towards the score and is not shown: on \"floating panel\", six of `Popover`'s\n * notes say \"panel\", and printing all six buries the answer.\n */\n props: string[]\n}\n\n/**\n * Words that are in nearly every sentence, and so tell a search nothing.\n *\n * Measured: `ui:find \"truncate a long link\"` printed `Link`'s `external` prop,\n * because its note says \"in **a** new tab\" and the one-letter word matched\n * itself exactly. The minimum length in `alike` only guards a *prefix* match;\n * an exact match short-circuits it, which is right for `tab` and useless for `a`.\n */\nconst STOP = new Set([\n 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'can', 'do', 'for', 'from', 'how', 'i', 'if', 'in', 'is', 'it', 'its', 'me', 'my', 'no', 'not', 'of',\n 'on', 'or', 'our', 'so', 'that', 'the', 'their', 'them', 'then', 'there', 'they', 'this', 'to', 'up', 'was', 'we', 'what', 'when', 'which', 'with', 'you', 'your',\n])\n\n/**\n * The words of a piece of text, with names taken apart.\n *\n * `CommandPalette` is kept whole *and* split, so \"palette\" reaches it and\n * \"commandpalette\" still does. Prose is already words; this costs it nothing.\n */\nfunction words(text: string): string[] {\n const found: string[] = []\n for (const token of text.match(/[A-Za-z0-9]+/g) ?? []) {\n found.push(token.toLowerCase())\n const parts = token.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/)\n if (parts.length > 1) found.push(...parts.map((part) => part.toLowerCase()))\n }\n return found\n}\n\n/**\n * Whether a word of the query means a word of the text.\n *\n * Either being a prefix of the other covers \"scroll\" against \"scrolling\"; the\n * five-letter key covers \"scrolls\" against \"scrolling\", where neither is. Crude\n * on purpose \u2014 a stemmer is a dependency and a surprise, and the failure here\n * is an extra row in a list of five, not a wrong answer.\n *\n * **Three letters at least**, or a prefix match is no match at all: `person's`\n * puts the word \"s\" in the text, and every query on earth starts with some\n * letter. Measured \u2014 before this, \"scrolling\" found `Avatar`.\n */\nfunction alike(query: string, text: string): boolean {\n if (query === text) return true\n if (Math.min(query.length, text.length) < 3) return false\n if (text.startsWith(query) || query.startsWith(text)) return true\n return query.slice(0, 5) === text.slice(0, 5)\n}\n\nconst hit = (query: string, text: string) => words(text).some((word) => alike(query, word))\n\n/**\n * Search name, purpose, behaviours and variants.\n *\n * A row that answers more of the question comes first, whatever it scored:\n * \"floating panel\" has to reach `Popover`, whose purpose carries both words,\n * over `MenuPanel`, whose name carries one of them loudly.\n */\nexport function findInRegistry(registry: Registry, query: string, { limit = 5 }: { limit?: number } = {}): Finding[] {\n return findInRegistries([registry], query, { limit })\n}\n\n/**\n * Search several catalogues as one: the package's, the app's own, and any app\n * beside it (UIG-13).\n *\n * An app's pass-on is not a result of its own \u2014 it is the package's part, and\n * the package's entry answers for it, saying which apps hand it on. At the\n * same strength the package's part comes first: it is the one to reach for.\n * And a package part whose name carries one of the words comes before an app's\n * part answering as many words, whatever each scored: \"panel header\" put an\n * app's own `Header`, whose whole name is one of the words, above the package's\n * `ContainerHeader` (UIG-20). Only by name: a package part the words reach\n * through its notes alone would bury the app part that is the answer.\n */\nexport function findInRegistries(registries: Registry[], query: string, { limit = 5 }: { limit?: number } = {}): Finding[] {\n const all = words(query)\n // Drop the words that mean nothing. If the question was only those, keep them\n // rather than answer nothing at all.\n const carrying = all.filter((word) => word.length >= 3 && !STOP.has(word))\n const asked = carrying.length ? carrying : all\n if (asked.length === 0) return []\n\n const findings: Finding[] = []\n const entries = registries.flatMap((registry) => registry.entries).filter((entry) => entry.app?.class !== 're-export')\n for (const entry of entries) {\n let score = 0\n let matched = 0\n const where = new Set<string>()\n const byName = new Set<string>()\n const byNote = new Set<string>()\n for (const word of asked) {\n let best = 0\n if (entry.name.toLowerCase() === word) {\n best = 100\n where.add('name')\n } else if (hit(word, entry.name)) {\n best = 40\n where.add('name')\n }\n if (hit(word, entry.purpose)) {\n best = Math.max(best, 20)\n where.add('purpose')\n }\n for (const owned of entry.ownsBehaviours) {\n if (hit(word, owned.behaviour) || hit(word, owned.id)) {\n best = Math.max(best, 15)\n where.add('behaviour')\n }\n }\n for (const variant of entry.variants) {\n if (variant.values.some((value) => hit(word, value)) || hit(word, variant.prop)) {\n best = Math.max(best, 8)\n where.add('variant')\n byName.add(variant.prop)\n }\n }\n // What it can do. \"does Link already truncate?\" is the question this\n // answers, and answering it is what stops the thing being built twice.\n for (const prop of entry.props) {\n // A prop the question named by name is the answer; one whose note\n // happens to carry the word is a hint. Named ones go first, so a short\n // answer shows the right lines.\n if (hit(word, prop.name)) {\n best = Math.max(best, 14)\n where.add('prop')\n byName.add(prop.name)\n } else if (prop.note !== null && hit(word, prop.note)) {\n best = Math.max(best, 10)\n where.add('prop')\n byNote.add(prop.name)\n }\n }\n if (best > 0) matched += 1\n score += best\n }\n if (matched > 0) findings.push({ entry, matched, score, where: [...where], props: [...byName] })\n }\n\n const fromPackage = (finding: Finding) => (finding.entry.app === null ? 0 : 1)\n const namedInPackage = (finding: Finding) => (finding.entry.app === null && finding.where.includes('name') ? 0 : 1)\n findings.sort(\n (a, b) =>\n b.matched - a.matched || namedInPackage(a) - namedInPackage(b) || b.score - a.score || fromPackage(a) - fromPackage(b) || a.entry.name.localeCompare(b.entry.name),\n )\n return findings.slice(0, limit)\n}\n\n/** `http://localhost:6008/?path=/docs/overlays-popover--docs`, or `null` where the entry has no page. */\nexport function docsLink(registry: Registry, entry: RegistryEntry): string | null {\n if (!entry.docsId) return null\n return registry.storybook.devUrl + registry.storybook.docsPath.replace('{docsId}', entry.docsId)\n}\n\n/** What an app's part is, in the words the search prints. */\nconst CLASS_WORDS: Record<string, string> = {\n 'one-off': 'used in one place',\n reusable: 'used in several places',\n 'promote-candidate': 'used in several places; could move into the package',\n unused: 'used nowhere in the app',\n}\n\n/** What the command prints: the import line first, because that is what the reader came for. */\nexport function formatFindings(searched: Registry | Registry[], findings: Finding[], query: string): string {\n const registries = Array.isArray(searched) ? searched : [searched]\n if (findings.length === 0) {\n return [\n `Nothing in ${registries.map((registry) => registry.builtFrom.repo).join(', ')} matches \"${query}\".`,\n 'If nothing here does what you need, say so and ask \u2014 do not invent a component.',\n ].join('\\n')\n }\n const registryOf = (entry: RegistryEntry) => registries.find((registry) => registry.builtFrom.repo === entry.repo) ?? registries[0]\n // Which apps hand a package part on, and from where: in Peek, `Button` is\n // imported from `@/components/ui/Button`, and the answer should say so.\n const handedOn = new Map<string, string[]>()\n for (const registry of registries) {\n for (const entry of registry.entries) {\n if (entry.app?.class !== 're-export' || entry.app.handsOn === null) continue\n handedOn.set(entry.app.handsOn, [...(handedOn.get(entry.app.handsOn) ?? []), `${entry.repo} hands it on${entry.name === entry.app.handsOn ? '' : ` as ${entry.name}`} from ${entry.importPath}`])\n }\n }\n\n const blocks = findings.map((finding) => {\n const { entry } = finding\n const facts = entry.app\n const lines = [\n `${entry.name} \u00B7 ${facts ? CLASS_WORDS[facts.class] ?? facts.class : entry.kind} \u00B7 ${entry.repo} (matched ${finding.where.join(', ')})`,\n ` ${entry.purpose}`,\n facts?.defaultExport ? ` import ${entry.name} from '${entry.importPath}'` : ` import { ${entry.name} } from '${entry.importPath}'`,\n ]\n if (!facts) for (const said of handedOn.get(entry.name) ?? []) lines.push(` ${said}`)\n if (facts?.packageNamesake) lines.push(` the package has a ${facts.packageNamesake} too: check it first`)\n if (facts && facts.usedIn.length) lines.push(` used in ${facts.usedIn.slice(0, 2).join(', ')}${facts.usedIn.length > 2 ? ` and ${facts.usedIn.length - 2} more` : ''}`)\n if (entry.ownsBehaviours.length) lines.push(` owns: ${entry.ownsBehaviours.map((owned) => owned.behaviour).join(' \u00B7 ')}`)\n if (entry.variants.length) lines.push(` ${entry.variants.map((variant) => `${variant.prop}: ${variant.values.join(' | ')}`).join(' ')}`)\n // The props the question named, with their own line \u2014 not all of them. A\n // component with twenty props would bury its own answer.\n const shown = finding.props\n .filter((name) => !entry.variants.some((variant) => variant.prop === name))\n .slice(0, 3)\n .map((name) => entry.props.find((prop) => prop.name === name))\n for (const prop of shown) {\n if (prop) lines.push(` ${prop.name}: ${prop.takes}${prop.note ? ` \u2014 ${prop.note}` : ''}`)\n }\n if (entry.props.length) lines.push(` ${entry.props.length} props in all; see ${entry.docsId ? 'the page' : entry.sourceFile} for the rest`)\n const link = docsLink(registryOf(entry), entry)\n if (link) lines.push(` ${link}`)\n return lines.join('\\n')\n })\n\n return blocks.join('\\n\\n')\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }