@estiva-app/ui 0.45.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.
Files changed (61) hide show
  1. package/dist/CollapsibleSection.d.ts +7 -1
  2. package/dist/CollapsibleSection.d.ts.map +1 -1
  3. package/dist/InlineChip.d.ts +1 -1
  4. package/dist/NavItem.d.ts +11 -1
  5. package/dist/NavItem.d.ts.map +1 -1
  6. package/dist/SectionHeader.d.ts +9 -1
  7. package/dist/SectionHeader.d.ts.map +1 -1
  8. package/dist/eslint/index.js +2 -1
  9. package/dist/eslint/index.js.map +2 -2
  10. package/dist/eslint/no-restyled-part.d.ts.map +1 -1
  11. package/dist/gates/app-checks.d.ts.map +1 -1
  12. package/dist/gates/{chunk-P5W6O5TF.js → chunk-BS5JASHV.js} +14 -9
  13. package/dist/gates/{chunk-P5W6O5TF.js.map → chunk-BS5JASHV.js.map} +3 -3
  14. package/dist/gates/cli.js +1 -1
  15. package/dist/gates/index.js +2 -1
  16. package/dist/gates/index.js.map +2 -2
  17. package/dist/gates/status.d.ts +2 -0
  18. package/dist/gates/status.d.ts.map +1 -1
  19. package/dist/index.js +76 -37
  20. package/dist/index.js.map +2 -2
  21. package/dist/looks.d.ts +3 -0
  22. package/dist/looks.d.ts.map +1 -1
  23. package/dist/registry/{chunk-XK747STJ.js → chunk-4QWD3EBY.js} +5 -1
  24. package/dist/registry/chunk-4QWD3EBY.js.map +7 -0
  25. package/dist/registry/cli.js +4 -3
  26. package/dist/registry/cli.js.map +2 -2
  27. package/dist/registry/contract.d.ts +7 -0
  28. package/dist/registry/contract.d.ts.map +1 -1
  29. package/dist/registry/index.d.ts +1 -1
  30. package/dist/registry/index.d.ts.map +1 -1
  31. package/dist/registry/index.js +3 -1
  32. package/package.json +1 -1
  33. package/registry.json +51 -9
  34. package/skill/estiva-ui.md +14 -11
  35. package/src/Chip.tsx +1 -1
  36. package/src/CollapsibleSection.mdx +5 -0
  37. package/src/CollapsibleSection.stories.tsx +5 -0
  38. package/src/CollapsibleSection.tsx +10 -3
  39. package/src/CommandPalette.tsx +1 -1
  40. package/src/InlineChip.test.tsx +1 -1
  41. package/src/InlineChip.tsx +1 -1
  42. package/src/NavItem.mdx +9 -0
  43. package/src/NavItem.stories.tsx +23 -0
  44. package/src/NavItem.tsx +56 -6
  45. package/src/Reaction.tsx +1 -1
  46. package/src/SectionHeader.mdx +9 -0
  47. package/src/SectionHeader.stories.tsx +15 -0
  48. package/src/SectionHeader.tsx +28 -7
  49. package/src/eslint/no-restyled-part.ts +2 -1
  50. package/src/gates/app-checks.ts +1 -0
  51. package/src/gates/gates.test.ts +22 -0
  52. package/src/gates/status.ts +19 -8
  53. package/src/looks.ts +4 -0
  54. package/src/registry/cli.ts +2 -2
  55. package/src/registry/contract.test.ts +18 -2
  56. package/src/registry/contract.ts +10 -0
  57. package/src/registry/index.ts +1 -1
  58. package/src/registry/skill.test.ts +2 -0
  59. package/tailwind-preset.js +1 -0
  60. package/tokens.css +19 -4
  61. package/dist/registry/chunk-XK747STJ.js.map +0 -7
@@ -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
 
package/src/looks.ts CHANGED
@@ -42,3 +42,7 @@ export const CHIP_TEXT_CLASSES = 'text-chip signal:font-mono signal:text-small s
42
42
 
43
43
  /** Words typed straight into an input with no box of its own: SearchInput and ChipInput. */
44
44
  export const BARE_INPUT_CLASSES = 'bg-transparent text-text-primary placeholder:text-text-muted outline-none'
45
+
46
+ /** A sidebar row's shape and its words at rest, brightening under the pointer: NavItem, and SectionHeader's `row` (a folder among rows). */
47
+ export const SIDEBAR_ROW_CLASSES = 'rounded-md transition-colors'
48
+ export const SIDEBAR_ROW_TEXT_CLASSES = 'text-text-secondary hover:text-text-primary'
@@ -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([
@@ -81,6 +81,16 @@ export function linkProblems(registry: Registry, ids: ReadonlySet<string>): stri
81
81
  return problems
82
82
  }
83
83
 
84
+ /**
85
+ * The parts an app's page may name under When and When not: the package's and
86
+ * the app's own (B8). The one copy: `estiva-ui check` and `gates:status` both
87
+ * read it, so they cannot disagree. Status once knew the app's parts only, and
88
+ * read six of Peek's pages as broken that CI passed (UIG-26).
89
+ */
90
+ export function nameableParts(app: Registry, pkg: Registry): Set<string> {
91
+ return new Set([...pkg.entries, ...app.entries].map((e) => e.name))
92
+ }
93
+
84
94
  /**
85
95
  * Every way an app's catalogue breaks the contract, one line each, naming the
86
96
  * part, its file and what to do. Empty when it keeps it.
@@ -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 {
18
18
  CLASSES,
@@ -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"/)
@@ -175,6 +175,7 @@ export default {
175
175
  'accent-primary': 'var(--accent-primary)',
176
176
  'accent-hover': 'var(--accent-hover)',
177
177
  'accent-muted': 'var(--accent-muted)',
178
+ 'accent-text': 'var(--accent-text)',
178
179
  // semantic
179
180
  'info-default': 'var(--info-default)',
180
181
  'info-muted': 'var(--info-muted)',
package/tokens.css CHANGED
@@ -48,6 +48,9 @@
48
48
  --accent-primary: #8B5CF6;
49
49
  --accent-hover: #7C3AED;
50
50
  --accent-muted: #EDE9FE;
51
+ /* The accent as TEXT: a tag's words, a mention. The accent itself unless a
52
+ theme's accent is too dark to read as text on its own ground (ship). */
53
+ --accent-text: #8B5CF6;
51
54
  --text-interactive: #8B5CF6;
52
55
 
53
56
  /* semantic */
@@ -129,6 +132,7 @@
129
132
  --accent-primary: #A78BFA;
130
133
  --accent-hover: #8B5CF6;
131
134
  --accent-muted: #2E2554;
135
+ --accent-text: #A78BFA; /* the accent as text: the accent itself */
132
136
  --text-interactive: #A78BFA;
133
137
 
134
138
  /* semantic */
@@ -213,6 +217,7 @@
213
217
  --accent-primary: #56c8ff;
214
218
  --accent-hover: #8ad6ff;
215
219
  --accent-muted: rgba(86, 200, 255, 0.11);
220
+ --accent-text: #56c8ff; /* the accent as text: the accent itself */
216
221
  --text-interactive: #8ad6ff;
217
222
 
218
223
  /* semantic (amber is the attention color) */
@@ -303,12 +308,21 @@
303
308
  /* accent — blue with a touch of purple; light text on it reads at 4.9:1 */
304
309
  --accent-primary: #4b58d4;
305
310
  --accent-hover: #5c69dc;
306
- --accent-muted: #1c2144;
307
- --text-interactive: #4b58d4;
311
+ /* Grey, not navy (Katerina, 30 September): the navy behind a tag, a
312
+ mention, a reaction and an info banner was hard to look at. The blue is
313
+ only the words now — the same grey a neutral tag sits on. */
314
+ --accent-muted: var(--bg-inset);
315
+ /* The accent as text: #4b58d4 read at 2.7:1 on a tag and 3.1:1 on the
316
+ page. This reads at 6.6:1 on a tag (Katerina's pick, "2", 30 September). */
317
+ --accent-text: #9ca3e7;
318
+ --text-interactive: #9ca3e7;
308
319
 
309
320
  /* semantic */
310
- --info-default: #5b7be8;
311
- --info-muted: #1a2342;
321
+ /* Info is only ever text and a small icon (a link, an info banner, an info
322
+ tag, Ship's In Progress icon): lightened with the accent, 4.0:1 → 6.9:1
323
+ on its banner. */
324
+ --info-default: #94a9f0;
325
+ --info-muted: var(--bg-inset);
312
326
  --warning-default: #e0b04a;
313
327
  --warning-muted: #3a2f14;
314
328
  --success-default: #46c08a;
@@ -398,6 +412,7 @@
398
412
  --accent-primary: #5e69d1; /* --focus-ring-color, the selection's colour */
399
413
  --accent-hover: #6b75df; /* not named in Linear's set: the indigo beside it in the app's own values */
400
414
  --accent-muted: #EDE9FE; /* light */
415
+ --accent-text: #5e69d1; /* the accent as text: the accent itself */
401
416
 
402
417
  /* semantic — light, until Linear's are read */
403
418
  --info-default: #3B82F6;
@@ -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
- }