@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.
- package/dist/CollapsibleSection.d.ts +7 -1
- package/dist/CollapsibleSection.d.ts.map +1 -1
- package/dist/InlineChip.d.ts +1 -1
- package/dist/NavItem.d.ts +11 -1
- package/dist/NavItem.d.ts.map +1 -1
- package/dist/SectionHeader.d.ts +9 -1
- package/dist/SectionHeader.d.ts.map +1 -1
- package/dist/eslint/index.js +2 -1
- package/dist/eslint/index.js.map +2 -2
- package/dist/eslint/no-restyled-part.d.ts.map +1 -1
- package/dist/gates/app-checks.d.ts.map +1 -1
- package/dist/gates/{chunk-P5W6O5TF.js → chunk-BS5JASHV.js} +14 -9
- package/dist/gates/{chunk-P5W6O5TF.js.map → chunk-BS5JASHV.js.map} +3 -3
- package/dist/gates/cli.js +1 -1
- package/dist/gates/index.js +2 -1
- package/dist/gates/index.js.map +2 -2
- package/dist/gates/status.d.ts +2 -0
- package/dist/gates/status.d.ts.map +1 -1
- package/dist/index.js +76 -37
- package/dist/index.js.map +2 -2
- package/dist/looks.d.ts +3 -0
- package/dist/looks.d.ts.map +1 -1
- package/dist/registry/{chunk-XK747STJ.js → chunk-4QWD3EBY.js} +5 -1
- package/dist/registry/chunk-4QWD3EBY.js.map +7 -0
- package/dist/registry/cli.js +4 -3
- package/dist/registry/cli.js.map +2 -2
- package/dist/registry/contract.d.ts +7 -0
- package/dist/registry/contract.d.ts.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +3 -1
- package/package.json +1 -1
- package/registry.json +51 -9
- package/skill/estiva-ui.md +14 -11
- package/src/Chip.tsx +1 -1
- package/src/CollapsibleSection.mdx +5 -0
- package/src/CollapsibleSection.stories.tsx +5 -0
- package/src/CollapsibleSection.tsx +10 -3
- package/src/CommandPalette.tsx +1 -1
- package/src/InlineChip.test.tsx +1 -1
- package/src/InlineChip.tsx +1 -1
- package/src/NavItem.mdx +9 -0
- package/src/NavItem.stories.tsx +23 -0
- package/src/NavItem.tsx +56 -6
- package/src/Reaction.tsx +1 -1
- package/src/SectionHeader.mdx +9 -0
- package/src/SectionHeader.stories.tsx +15 -0
- package/src/SectionHeader.tsx +28 -7
- package/src/eslint/no-restyled-part.ts +2 -1
- package/src/gates/app-checks.ts +1 -0
- package/src/gates/gates.test.ts +22 -0
- package/src/gates/status.ts +19 -8
- package/src/looks.ts +4 -0
- package/src/registry/cli.ts +2 -2
- package/src/registry/contract.test.ts +18 -2
- package/src/registry/contract.ts +10 -0
- package/src/registry/index.ts +1 -1
- package/src/registry/skill.test.ts +2 -0
- package/tailwind-preset.js +1 -0
- package/tokens.css +19 -4
- package/dist/registry/chunk-XK747STJ.js.map +0 -7
package/src/gates/status.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
package/src/registry/cli.ts
CHANGED
|
@@ -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 =
|
|
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 {
|
|
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([
|
package/src/registry/contract.ts
CHANGED
|
@@ -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.
|
package/src/registry/index.ts
CHANGED
|
@@ -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"/)
|
package/tailwind-preset.js
CHANGED
|
@@ -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
|
-
|
|
307
|
-
|
|
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
|
-
|
|
311
|
-
|
|
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
|
-
}
|