@fragments-sdk/classifier 0.2.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 (51) hide show
  1. package/LICENSE +84 -0
  2. package/dist/index.d.ts +184 -0
  3. package/dist/index.js +1856 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +45 -0
  6. package/src/__tests__/combiner.test.ts +222 -0
  7. package/src/__tests__/fixtures.ts +96 -0
  8. package/src/ai/__tests__/cache-key.test.ts +50 -0
  9. package/src/ai/__tests__/prompt.test.ts +95 -0
  10. package/src/ai/__tests__/schema.test.ts +145 -0
  11. package/src/ai/__tests__/secret-scrub.test.ts +70 -0
  12. package/src/ai/__tests__/signal.test.ts +94 -0
  13. package/src/ai/cache-key.ts +46 -0
  14. package/src/ai/index.ts +42 -0
  15. package/src/ai/prompt.ts +154 -0
  16. package/src/ai/schema.ts +148 -0
  17. package/src/ai/secret-scrub.ts +116 -0
  18. package/src/ai/signal.ts +81 -0
  19. package/src/ai/version.ts +15 -0
  20. package/src/canonical-vocab/resolve-by-html-element.ts +72 -0
  21. package/src/combiner/__tests__/band.test.ts +155 -0
  22. package/src/combiner/__tests__/group.test.ts +85 -0
  23. package/src/combiner/__tests__/rank.test.ts +54 -0
  24. package/src/combiner/band.ts +85 -0
  25. package/src/combiner/group.ts +62 -0
  26. package/src/combiner/rank.ts +57 -0
  27. package/src/combiner.ts +124 -0
  28. package/src/index.ts +76 -0
  29. package/src/signals/__tests__/aria-role.test.ts +53 -0
  30. package/src/signals/__tests__/barrel-export.test.ts +29 -0
  31. package/src/signals/__tests__/html-root.test.ts +55 -0
  32. package/src/signals/__tests__/input-type.test.ts +58 -0
  33. package/src/signals/__tests__/library-reexport.test.ts +68 -0
  34. package/src/signals/__tests__/name-match.test.ts +43 -0
  35. package/src/signals/__tests__/path-hint.test.ts +55 -0
  36. package/src/signals/__tests__/prop-fingerprint.test.ts +105 -0
  37. package/src/signals/__tests__/registry.test.ts +27 -0
  38. package/src/signals/aria-role.ts +94 -0
  39. package/src/signals/barrel-export.ts +28 -0
  40. package/src/signals/html-root.ts +85 -0
  41. package/src/signals/index.ts +39 -0
  42. package/src/signals/input-type.ts +63 -0
  43. package/src/signals/library-reexport.ts +70 -0
  44. package/src/signals/name-match.ts +92 -0
  45. package/src/signals/path-hint.ts +94 -0
  46. package/src/signals/prop-fingerprint.ts +121 -0
  47. package/src/types.ts +58 -0
  48. package/src/vocabulary/canonicals.ts +106 -0
  49. package/src/vocabulary/library-map.ts +301 -0
  50. package/src/vocabulary/prop-fingerprints.ts +433 -0
  51. package/src/vocabulary/synonyms.ts +130 -0
@@ -0,0 +1,121 @@
1
+ // PROP_FINGERPRINT — `01-architecture.md` §9.5.
2
+ //
3
+ // Medium-precision signal (base weight 0.3). For each canonical fingerprint:
4
+ // - All required tests must pass to fire.
5
+ // - Optional matches add 0.05 each, capped so total weight ≤ 0.6.
6
+ // - Anti-fingerprint hits subtract 0.15 each (per-canonical).
7
+ // - Polymorphic prop (`as`/`component`/...) reduces every hypothesis by 0.1
8
+ // (applied at emit time).
9
+
10
+ import type { PropFact, UniversalComponentFact } from '@fragments-sdk/extract';
11
+
12
+ import type { CanonicalId, SignalExtractor, SignalRecord } from '../types.js';
13
+ import {
14
+ POLYMORPHIC_PROP_NAMES,
15
+ PROP_FINGERPRINTS,
16
+ type AntiPropTest,
17
+ type OptionalPropTest,
18
+ type RequiredPropTest,
19
+ } from '../vocabulary/prop-fingerprints.js';
20
+
21
+ const BASE_WEIGHT = 0.3;
22
+ const OPTIONAL_INCREMENT = 0.05;
23
+ const ANTI_DECREMENT = 0.15;
24
+ const POLYMORPHIC_PENALTY = 0.1;
25
+ const MAX_WEIGHT = 0.6;
26
+
27
+ function findProp(props: ReadonlyArray<PropFact>, name: string): PropFact | undefined {
28
+ // Aliases like `aria-label` come through verbatim; case-sensitive match.
29
+ return props.find((p) => p.name === name);
30
+ }
31
+
32
+ function requiredMatches(
33
+ props: ReadonlyArray<PropFact>,
34
+ test: RequiredPropTest,
35
+ ): { matched: true; via: string } | { matched: false } {
36
+ for (const candidate of test.oneOf) {
37
+ const prop = findProp(props, candidate.name);
38
+ if (!prop) continue;
39
+ if (candidate.typeMatcher && !candidate.typeMatcher(prop.typeText)) continue;
40
+ return { matched: true, via: candidate.name };
41
+ }
42
+ return { matched: false };
43
+ }
44
+
45
+ function optionalMatches(
46
+ props: ReadonlyArray<PropFact>,
47
+ test: OptionalPropTest,
48
+ ): boolean {
49
+ const prop = findProp(props, test.name);
50
+ if (!prop) return false;
51
+ if (test.typeMatcher && !test.typeMatcher(prop.typeText)) return false;
52
+ return true;
53
+ }
54
+
55
+ function antiMatches(
56
+ props: ReadonlyArray<PropFact>,
57
+ test: AntiPropTest,
58
+ ): boolean {
59
+ const prop = findProp(props, test.name);
60
+ if (!prop) return false;
61
+ if (test.typeMatcher && !test.typeMatcher(prop.typeText)) return false;
62
+ return true;
63
+ }
64
+
65
+ function isPolymorphic(props: ReadonlyArray<PropFact>): boolean {
66
+ return props.some((prop) => POLYMORPHIC_PROP_NAMES.has(prop.name));
67
+ }
68
+
69
+ const propFingerprint: SignalExtractor = (
70
+ ucf: UniversalComponentFact,
71
+ ): SignalRecord[] => {
72
+ const polymorphic = isPolymorphic(ucf.props);
73
+ const out: SignalRecord[] = [];
74
+
75
+ for (const [canonical, fingerprint] of PROP_FINGERPRINTS.entries()) {
76
+ const requiredHits: string[] = [];
77
+ let allRequired = true;
78
+ for (const required of fingerprint.required) {
79
+ const result = requiredMatches(ucf.props, required);
80
+ if (!result.matched) {
81
+ allRequired = false;
82
+ break;
83
+ }
84
+ requiredHits.push(result.via);
85
+ }
86
+ if (!allRequired) continue;
87
+
88
+ const optionalHits: string[] = [];
89
+ for (const optional of fingerprint.optional) {
90
+ if (optionalMatches(ucf.props, optional)) optionalHits.push(optional.name);
91
+ }
92
+
93
+ const antiHits: string[] = [];
94
+ for (const anti of fingerprint.anti) {
95
+ if (antiMatches(ucf.props, anti)) antiHits.push(anti.name);
96
+ }
97
+
98
+ let weight = BASE_WEIGHT + optionalHits.length * OPTIONAL_INCREMENT;
99
+ weight -= antiHits.length * ANTI_DECREMENT;
100
+ if (polymorphic) weight -= POLYMORPHIC_PENALTY;
101
+
102
+ if (weight <= 0) continue;
103
+ if (weight > MAX_WEIGHT) weight = MAX_WEIGHT;
104
+
105
+ out.push({
106
+ type: 'PROP_FINGERPRINT',
107
+ canonical: canonical as CanonicalId,
108
+ weight,
109
+ evidence: {
110
+ requiredHits,
111
+ optionalHits,
112
+ antiHits,
113
+ polymorphic,
114
+ },
115
+ });
116
+ }
117
+
118
+ return out;
119
+ };
120
+
121
+ export default propFingerprint;
package/src/types.ts ADDED
@@ -0,0 +1,58 @@
1
+ // @fragments-sdk/classifier — public types.
2
+ //
3
+ // Mirrors `apps/cloud/docs/canonical-primitive-map/01-architecture.md` §9.
4
+ // Each heuristic signal extractor is a pure function `(ucf) => SignalRecord[]`.
5
+ // The combiner (brief 04) iterates these and applies §10 confidence math.
6
+
7
+ import type { UniversalComponentFact } from '@fragments-sdk/extract';
8
+
9
+ export type CanonicalId = string & { readonly __brand: 'CanonicalId' };
10
+
11
+ export function canonicalId(value: string): CanonicalId {
12
+ return value as CanonicalId;
13
+ }
14
+
15
+ export type SignalType =
16
+ | 'LIBRARY_REEXPORT'
17
+ | 'HTML_ROOT'
18
+ | 'ARIA_ROLE'
19
+ | 'INPUT_TYPE'
20
+ | 'PROP_FINGERPRINT'
21
+ | 'NAME_MATCH'
22
+ | 'PATH_HINT'
23
+ | 'BARREL_EXPORT'
24
+ | 'AI_SEMANTIC';
25
+
26
+ export type HeuristicSignalType = Exclude<SignalType, 'AI_SEMANTIC'>;
27
+
28
+ export interface SignalRecord {
29
+ type: SignalType;
30
+ canonical: CanonicalId;
31
+ weight: number;
32
+ evidence: Record<string, unknown>;
33
+ }
34
+
35
+ export type SignalExtractor = (ucf: UniversalComponentFact) => SignalRecord[];
36
+
37
+ export type SignalRegistry = Record<HeuristicSignalType, SignalExtractor>;
38
+
39
+ // `01-architecture.md` §10.3 — confidence band assigned by the combiner.
40
+ export type Band = 'auto' | 'suggested' | 'possible' | 'unknown';
41
+
42
+ export interface ClassificationAlternate {
43
+ canonical: CanonicalId;
44
+ confidence: number;
45
+ signals: SignalRecord[];
46
+ }
47
+
48
+ // `01-architecture.md` §10.5 — "show your work" payload.
49
+ export interface Classification {
50
+ canonical: CanonicalId | 'unknown';
51
+ confidence: number; // adjusted, post-disagreement-penalty
52
+ rawConfidence: number; // pre-penalty composition
53
+ band: Band;
54
+ signals: SignalRecord[]; // contributing signals for the leading canonical
55
+ alternates: ClassificationAlternate[];
56
+ classifierVersion: string;
57
+ vocabVersion: string;
58
+ }
@@ -0,0 +1,106 @@
1
+ // Vocabulary v0 — ~50 canonical primitives across six categories.
2
+ // Verbatim port of `01-architecture.md` §5.
3
+
4
+ import { canonicalId, type CanonicalId } from '../types.js';
5
+
6
+ export type CanonicalCategory =
7
+ | 'inputs'
8
+ | 'overlays'
9
+ | 'navigation'
10
+ | 'feedback'
11
+ | 'data'
12
+ | 'layout';
13
+
14
+ export interface CanonicalEntry {
15
+ id: CanonicalId;
16
+ category: CanonicalCategory;
17
+ primaryHtmlElement?: string;
18
+ primaryAriaRole?: string;
19
+ }
20
+
21
+ const c = canonicalId;
22
+
23
+ export const VOCAB_V0_VERSION = 'vocab_v0';
24
+
25
+ export const VOCAB_V0: ReadonlyArray<CanonicalEntry> = [
26
+ // §5.1 inputs & form controls
27
+ { id: c('Button'), category: 'inputs', primaryHtmlElement: 'button', primaryAriaRole: 'button' },
28
+ { id: c('IconButton'), category: 'inputs', primaryHtmlElement: 'button', primaryAriaRole: 'button' },
29
+ { id: c('ToggleButton'), category: 'inputs', primaryHtmlElement: 'button', primaryAriaRole: 'button' },
30
+ { id: c('Input'), category: 'inputs', primaryHtmlElement: 'input', primaryAriaRole: 'textbox' },
31
+ { id: c('Textarea'), category: 'inputs', primaryHtmlElement: 'textarea', primaryAriaRole: 'textbox' },
32
+ { id: c('NumberInput'), category: 'inputs', primaryHtmlElement: 'input', primaryAriaRole: 'spinbutton' },
33
+ { id: c('PasswordInput'), category: 'inputs', primaryHtmlElement: 'input', primaryAriaRole: 'textbox' },
34
+ { id: c('Checkbox'), category: 'inputs', primaryHtmlElement: 'input', primaryAriaRole: 'checkbox' },
35
+ { id: c('Radio'), category: 'inputs', primaryHtmlElement: 'input', primaryAriaRole: 'radio' },
36
+ { id: c('RadioGroup'), category: 'inputs', primaryAriaRole: 'radiogroup' },
37
+ { id: c('Switch'), category: 'inputs', primaryHtmlElement: 'input', primaryAriaRole: 'switch' },
38
+ { id: c('Slider'), category: 'inputs', primaryAriaRole: 'slider' },
39
+ { id: c('Select'), category: 'inputs', primaryHtmlElement: 'select', primaryAriaRole: 'combobox' },
40
+ { id: c('Combobox'), category: 'inputs', primaryAriaRole: 'combobox' },
41
+ { id: c('MultiSelect'), category: 'inputs', primaryAriaRole: 'listbox' },
42
+ { id: c('DatePicker'), category: 'inputs' },
43
+ { id: c('TimePicker'), category: 'inputs' },
44
+ { id: c('Calendar'), category: 'inputs' },
45
+ { id: c('Form'), category: 'inputs', primaryHtmlElement: 'form', primaryAriaRole: 'form' },
46
+ { id: c('Field'), category: 'inputs' },
47
+ { id: c('Label'), category: 'inputs', primaryHtmlElement: 'label' },
48
+ { id: c('FieldError'), category: 'inputs', primaryAriaRole: 'alert' },
49
+ { id: c('FieldHint'), category: 'inputs' },
50
+
51
+ // §5.2 disclosure & overlays
52
+ { id: c('Dialog'), category: 'overlays', primaryHtmlElement: 'dialog', primaryAriaRole: 'dialog' },
53
+ { id: c('AlertDialog'), category: 'overlays', primaryHtmlElement: 'dialog', primaryAriaRole: 'alertdialog' },
54
+ { id: c('Drawer'), category: 'overlays', primaryAriaRole: 'dialog' },
55
+ { id: c('Sheet'), category: 'overlays', primaryAriaRole: 'dialog' },
56
+ { id: c('Popover'), category: 'overlays', primaryAriaRole: 'dialog' },
57
+ { id: c('Tooltip'), category: 'overlays', primaryAriaRole: 'tooltip' },
58
+ { id: c('HoverCard'), category: 'overlays' },
59
+ { id: c('Toast'), category: 'overlays', primaryAriaRole: 'status' },
60
+
61
+ // §5.3 navigation & disclosure-group
62
+ { id: c('Tabs'), category: 'navigation', primaryAriaRole: 'tablist' },
63
+ { id: c('Accordion'), category: 'navigation' },
64
+ { id: c('Disclosure'), category: 'navigation', primaryHtmlElement: 'details' },
65
+ { id: c('Menu'), category: 'navigation', primaryAriaRole: 'menu' },
66
+ { id: c('ContextMenu'), category: 'navigation', primaryAriaRole: 'menu' },
67
+ { id: c('MenuBar'), category: 'navigation', primaryAriaRole: 'menubar' },
68
+ { id: c('Breadcrumb'), category: 'navigation', primaryHtmlElement: 'nav', primaryAriaRole: 'navigation' },
69
+ { id: c('Pagination'), category: 'navigation', primaryHtmlElement: 'nav', primaryAriaRole: 'navigation' },
70
+ { id: c('Stepper'), category: 'navigation' },
71
+ { id: c('NavigationMenu'), category: 'navigation', primaryHtmlElement: 'nav', primaryAriaRole: 'navigation' },
72
+
73
+ // §5.4 display & feedback
74
+ { id: c('Card'), category: 'feedback' },
75
+ { id: c('Badge'), category: 'feedback' },
76
+ { id: c('Chip'), category: 'feedback' },
77
+ { id: c('Tag'), category: 'feedback' },
78
+ { id: c('Avatar'), category: 'feedback' },
79
+ { id: c('Alert'), category: 'feedback', primaryAriaRole: 'alert' },
80
+ { id: c('Banner'), category: 'feedback' },
81
+ { id: c('Skeleton'), category: 'feedback' },
82
+ { id: c('Spinner'), category: 'feedback', primaryAriaRole: 'progressbar' },
83
+ { id: c('Progress'), category: 'feedback', primaryHtmlElement: 'progress', primaryAriaRole: 'progressbar' },
84
+ { id: c('Separator'), category: 'feedback', primaryHtmlElement: 'hr', primaryAriaRole: 'separator' },
85
+
86
+ // §5.5 data display
87
+ { id: c('Table'), category: 'data', primaryHtmlElement: 'table', primaryAriaRole: 'table' },
88
+ { id: c('DataTable'), category: 'data', primaryHtmlElement: 'table', primaryAriaRole: 'grid' },
89
+ { id: c('List'), category: 'data', primaryHtmlElement: 'ul', primaryAriaRole: 'list' },
90
+ { id: c('Tree'), category: 'data', primaryAriaRole: 'tree' },
91
+ { id: c('ScrollArea'), category: 'data' },
92
+
93
+ // §5.6 layout primitives (controversial — see §22)
94
+ { id: c('Stack'), category: 'layout' },
95
+ { id: c('Grid'), category: 'layout' },
96
+ { id: c('Box'), category: 'layout' },
97
+ { id: c('Container'), category: 'layout' },
98
+ ];
99
+
100
+ export const VOCAB_V0_INDEX: ReadonlyMap<string, CanonicalEntry> = new Map(
101
+ VOCAB_V0.map((entry) => [entry.id as string, entry]),
102
+ );
103
+
104
+ export function isCanonicalId(value: string): value is CanonicalId {
105
+ return VOCAB_V0_INDEX.has(value);
106
+ }
@@ -0,0 +1,301 @@
1
+ // LIBRARY_REEXPORT lookup table — `01-architecture.md` §9.1.
2
+ //
3
+ // Keyed by `package → importedName → canonical`. The signal fires when a
4
+ // component imports a known primitive AND uses the local binding as a JSX
5
+ // root. Sub-primitive policy (§9.1 final paragraph): only root-aligned wrappers
6
+ // emit, so we map only the root-level identifier per package and let
7
+ // compound-children detection on the parent classify the sub-parts.
8
+
9
+ import { canonicalId, type CanonicalId } from '../types.js';
10
+
11
+ export interface LibraryMapEntry {
12
+ canonical: CanonicalId;
13
+ }
14
+
15
+ export type LibraryMap = ReadonlyMap<
16
+ string,
17
+ ReadonlyMap<string, LibraryMapEntry>
18
+ >;
19
+
20
+ const c = canonicalId;
21
+
22
+ function entries(
23
+ pkg: string,
24
+ ...rows: Array<[string, CanonicalId]>
25
+ ): [string, ReadonlyMap<string, LibraryMapEntry>] {
26
+ return [pkg, new Map(rows.map(([imported, canon]) => [imported, { canonical: canon }]))];
27
+ }
28
+
29
+ // Default exports use the literal key 'default'. Namespace imports (`* as ns`)
30
+ // are matched per-name by walking the namespace usage at signal time.
31
+ export const LIBRARY_MAP: LibraryMap = new Map([
32
+ // ── Radix UI ──────────────────────────────────────────────────────
33
+ entries('@radix-ui/react-dialog', ['Root', c('Dialog')], ['default', c('Dialog')]),
34
+ entries('@radix-ui/react-tooltip', ['Root', c('Tooltip')]),
35
+ entries('@radix-ui/react-popover', ['Root', c('Popover')]),
36
+ entries('@radix-ui/react-tabs', ['Root', c('Tabs')]),
37
+ entries('@radix-ui/react-accordion', ['Root', c('Accordion')]),
38
+ entries('@radix-ui/react-checkbox', ['Root', c('Checkbox')]),
39
+ entries('@radix-ui/react-switch', ['Root', c('Switch')]),
40
+ entries('@radix-ui/react-slider', ['Root', c('Slider')]),
41
+ entries('@radix-ui/react-select', ['Root', c('Select')]),
42
+ entries('@radix-ui/react-radio-group', ['Root', c('RadioGroup')]),
43
+ entries('@radix-ui/react-progress', ['Root', c('Progress')]),
44
+ entries('@radix-ui/react-separator', ['Root', c('Separator')]),
45
+ entries('@radix-ui/react-alert-dialog', ['Root', c('AlertDialog')]),
46
+ entries('@radix-ui/react-context-menu', ['Root', c('ContextMenu')]),
47
+ entries('@radix-ui/react-dropdown-menu', ['Root', c('Menu')]),
48
+ entries('@radix-ui/react-hover-card', ['Root', c('HoverCard')]),
49
+ entries('@radix-ui/react-menubar', ['Root', c('MenuBar')]),
50
+ entries('@radix-ui/react-navigation-menu', ['Root', c('NavigationMenu')]),
51
+ entries('@radix-ui/react-scroll-area', ['Root', c('ScrollArea')]),
52
+ entries('@radix-ui/react-toast', ['Root', c('Toast')]),
53
+ entries('@radix-ui/react-toggle', ['Root', c('ToggleButton')]),
54
+ entries('@radix-ui/react-toggle-group', ['Root', c('ToggleButton')]),
55
+ entries('@radix-ui/react-avatar', ['Root', c('Avatar')]),
56
+ entries('@radix-ui/react-label', ['Root', c('Label')]),
57
+ entries('@radix-ui/react-form', ['Root', c('Form')]),
58
+
59
+ // ── Base UI ───────────────────────────────────────────────────────
60
+ entries('@base-ui-components/react/dialog', ['Root', c('Dialog')], ['Dialog', c('Dialog')]),
61
+ entries('@base-ui-components/react/tooltip', ['Root', c('Tooltip')], ['Tooltip', c('Tooltip')]),
62
+ entries('@base-ui-components/react/menu', ['Root', c('Menu')], ['Menu', c('Menu')]),
63
+ entries('@base-ui-components/react/select', ['Root', c('Select')], ['Select', c('Select')]),
64
+ entries('@base-ui-components/react/combobox', ['Root', c('Combobox')], ['Combobox', c('Combobox')]),
65
+ entries('@base-ui-components/react/tabs', ['Root', c('Tabs')], ['Tabs', c('Tabs')]),
66
+ entries('@base-ui-components/react/popover', ['Root', c('Popover')], ['Popover', c('Popover')]),
67
+ entries('@base-ui-components/react/checkbox', ['Root', c('Checkbox')], ['Checkbox', c('Checkbox')]),
68
+ entries('@base-ui-components/react/switch', ['Root', c('Switch')], ['Switch', c('Switch')]),
69
+ entries('@base-ui-components/react/slider', ['Root', c('Slider')], ['Slider', c('Slider')]),
70
+ entries('@base-ui-components/react/radio-group', ['Root', c('RadioGroup')], ['RadioGroup', c('RadioGroup')]),
71
+ entries('@base-ui-components/react/accordion', ['Root', c('Accordion')], ['Accordion', c('Accordion')]),
72
+ entries('@base-ui-components/react/separator', ['Root', c('Separator')], ['Separator', c('Separator')]),
73
+ entries('@base-ui-components/react/progress', ['Root', c('Progress')], ['Progress', c('Progress')]),
74
+ entries('@base-ui-components/react/scroll-area', ['Root', c('ScrollArea')]),
75
+ entries('@base-ui-components/react/toggle', ['Root', c('ToggleButton')]),
76
+ entries('@base-ui-components/react/toggle-group', ['Root', c('ToggleButton')]),
77
+ entries('@base-ui-components/react/alert-dialog', ['Root', c('AlertDialog')]),
78
+ entries('@base-ui-components/react/context-menu', ['Root', c('ContextMenu')]),
79
+ entries('@base-ui-components/react/menubar', ['Root', c('MenuBar')]),
80
+ entries('@base-ui-components/react/navigation-menu', ['Root', c('NavigationMenu')]),
81
+ entries('@base-ui-components/react/avatar', ['Root', c('Avatar')]),
82
+ entries('@base-ui-components/react/field', ['Root', c('Field')]),
83
+ entries('@base-ui-components/react/form', ['Root', c('Form')]),
84
+
85
+ // ── Headless UI ───────────────────────────────────────────────────
86
+ entries(
87
+ '@headlessui/react',
88
+ ['Dialog', c('Dialog')],
89
+ ['Menu', c('Menu')],
90
+ ['Listbox', c('Select')],
91
+ ['Combobox', c('Combobox')],
92
+ ['Switch', c('Switch')],
93
+ ['RadioGroup', c('RadioGroup')],
94
+ ['Tab', c('Tabs')],
95
+ ['Disclosure', c('Disclosure')],
96
+ ['Popover', c('Popover')],
97
+ ['Transition', c('Box')],
98
+ ),
99
+
100
+ // ── React Aria Components ─────────────────────────────────────────
101
+ entries(
102
+ 'react-aria-components',
103
+ ['Button', c('Button')],
104
+ ['ToggleButton', c('ToggleButton')],
105
+ ['Checkbox', c('Checkbox')],
106
+ ['Radio', c('Radio')],
107
+ ['RadioGroup', c('RadioGroup')],
108
+ ['Switch', c('Switch')],
109
+ ['Slider', c('Slider')],
110
+ ['Select', c('Select')],
111
+ ['ComboBox', c('Combobox')],
112
+ ['ListBox', c('Select')],
113
+ ['Menu', c('Menu')],
114
+ ['Dialog', c('Dialog')],
115
+ ['Modal', c('Dialog')],
116
+ ['Popover', c('Popover')],
117
+ ['Tooltip', c('Tooltip')],
118
+ ['Tabs', c('Tabs')],
119
+ ['DateField', c('DatePicker')],
120
+ ['DatePicker', c('DatePicker')],
121
+ ['Calendar', c('Calendar')],
122
+ ['NumberField', c('NumberInput')],
123
+ ['TextField', c('Input')],
124
+ ['SearchField', c('Input')],
125
+ ['ProgressBar', c('Progress')],
126
+ ['Breadcrumbs', c('Breadcrumb')],
127
+ ),
128
+
129
+ // ── MUI ───────────────────────────────────────────────────────────
130
+ entries('@mui/material', ['Button', c('Button')], ['default', c('Button')]),
131
+ entries('@mui/material/Button', ['default', c('Button')]),
132
+ entries('@mui/material/IconButton', ['default', c('IconButton')]),
133
+ entries('@mui/material/ToggleButton', ['default', c('ToggleButton')]),
134
+ entries('@mui/material/TextField', ['default', c('Input')]),
135
+ entries('@mui/material/OutlinedInput', ['default', c('Input')]),
136
+ entries('@mui/material/FilledInput', ['default', c('Input')]),
137
+ entries('@mui/material/Input', ['default', c('Input')]),
138
+ entries('@mui/material/InputBase', ['default', c('Input')]),
139
+ entries('@mui/material/Checkbox', ['default', c('Checkbox')]),
140
+ entries('@mui/material/Radio', ['default', c('Radio')]),
141
+ entries('@mui/material/RadioGroup', ['default', c('RadioGroup')]),
142
+ entries('@mui/material/Switch', ['default', c('Switch')]),
143
+ entries('@mui/material/Slider', ['default', c('Slider')]),
144
+ entries('@mui/material/Select', ['default', c('Select')]),
145
+ entries('@mui/material/Autocomplete', ['default', c('Combobox')]),
146
+ entries('@mui/material/Dialog', ['default', c('Dialog')]),
147
+ entries('@mui/material/Drawer', ['default', c('Drawer')]),
148
+ entries('@mui/material/Popover', ['default', c('Popover')]),
149
+ entries('@mui/material/Tooltip', ['default', c('Tooltip')]),
150
+ entries('@mui/material/Snackbar', ['default', c('Toast')]),
151
+ entries('@mui/material/Alert', ['default', c('Alert')]),
152
+ entries('@mui/material/Tabs', ['default', c('Tabs')]),
153
+ entries('@mui/material/Tab', ['default', c('Tabs')]),
154
+ entries('@mui/material/Accordion', ['default', c('Accordion')]),
155
+ entries('@mui/material/Menu', ['default', c('Menu')]),
156
+ entries('@mui/material/Breadcrumbs', ['default', c('Breadcrumb')]),
157
+ entries('@mui/material/Pagination', ['default', c('Pagination')]),
158
+ entries('@mui/material/Stepper', ['default', c('Stepper')]),
159
+ entries('@mui/material/Avatar', ['default', c('Avatar')]),
160
+ entries('@mui/material/Badge', ['default', c('Badge')]),
161
+ entries('@mui/material/Chip', ['default', c('Chip')]),
162
+ entries('@mui/material/Card', ['default', c('Card')]),
163
+ entries('@mui/material/Skeleton', ['default', c('Skeleton')]),
164
+ entries('@mui/material/CircularProgress', ['default', c('Spinner')]),
165
+ entries('@mui/material/LinearProgress', ['default', c('Progress')]),
166
+ entries('@mui/material/Divider', ['default', c('Separator')]),
167
+ entries('@mui/material/Table', ['default', c('Table')]),
168
+ entries('@mui/material/List', ['default', c('List')]),
169
+ entries('@mui/material/Stack', ['default', c('Stack')]),
170
+ entries('@mui/material/Grid', ['default', c('Grid')]),
171
+ entries('@mui/material/Box', ['default', c('Box')]),
172
+ entries('@mui/material/Container', ['default', c('Container')]),
173
+ entries('@mui/material/FormLabel', ['default', c('Label')]),
174
+ entries('@mui/material/FormHelperText', ['default', c('FieldHint')]),
175
+ entries('@mui/material/FormControl', ['default', c('Field')]),
176
+ entries('@mui/x-date-pickers/DatePicker', ['DatePicker', c('DatePicker')]),
177
+ entries('@mui/x-date-pickers/TimePicker', ['TimePicker', c('TimePicker')]),
178
+ entries('@mui/x-date-pickers/DateCalendar', ['DateCalendar', c('Calendar')]),
179
+ entries('@mui/x-data-grid', ['DataGrid', c('DataTable')]),
180
+
181
+ // ── Chakra ────────────────────────────────────────────────────────
182
+ entries(
183
+ '@chakra-ui/react',
184
+ ['Button', c('Button')],
185
+ ['IconButton', c('IconButton')],
186
+ ['Input', c('Input')],
187
+ ['Textarea', c('Textarea')],
188
+ ['NumberInput', c('NumberInput')],
189
+ ['Checkbox', c('Checkbox')],
190
+ ['Radio', c('Radio')],
191
+ ['RadioGroup', c('RadioGroup')],
192
+ ['Switch', c('Switch')],
193
+ ['Slider', c('Slider')],
194
+ ['Select', c('Select')],
195
+ ['Modal', c('Dialog')],
196
+ ['AlertDialog', c('AlertDialog')],
197
+ ['Drawer', c('Drawer')],
198
+ ['Popover', c('Popover')],
199
+ ['Tooltip', c('Tooltip')],
200
+ ['Toast', c('Toast')],
201
+ ['Alert', c('Alert')],
202
+ ['Tabs', c('Tabs')],
203
+ ['Accordion', c('Accordion')],
204
+ ['Menu', c('Menu')],
205
+ ['Breadcrumb', c('Breadcrumb')],
206
+ ['Avatar', c('Avatar')],
207
+ ['Badge', c('Badge')],
208
+ ['Tag', c('Tag')],
209
+ ['Card', c('Card')],
210
+ ['Skeleton', c('Skeleton')],
211
+ ['Spinner', c('Spinner')],
212
+ ['Progress', c('Progress')],
213
+ ['Divider', c('Separator')],
214
+ ['Table', c('Table')],
215
+ ['List', c('List')],
216
+ ['Stack', c('Stack')],
217
+ ['HStack', c('Stack')],
218
+ ['VStack', c('Stack')],
219
+ ['Grid', c('Grid')],
220
+ ['Box', c('Box')],
221
+ ['Container', c('Container')],
222
+ ['FormLabel', c('Label')],
223
+ ['FormHelperText', c('FieldHint')],
224
+ ['FormErrorMessage', c('FieldError')],
225
+ ['FormControl', c('Field')],
226
+ ),
227
+ entries('@chakra-ui/button', ['Button', c('Button')], ['IconButton', c('IconButton')]),
228
+
229
+ // ── Mantine ───────────────────────────────────────────────────────
230
+ entries(
231
+ '@mantine/core',
232
+ ['Button', c('Button')],
233
+ ['ActionIcon', c('IconButton')],
234
+ ['UnstyledButton', c('Button')],
235
+ ['TextInput', c('Input')],
236
+ ['PasswordInput', c('PasswordInput')],
237
+ ['NumberInput', c('NumberInput')],
238
+ ['Textarea', c('Textarea')],
239
+ ['Checkbox', c('Checkbox')],
240
+ ['Radio', c('Radio')],
241
+ ['Switch', c('Switch')],
242
+ ['Slider', c('Slider')],
243
+ ['RangeSlider', c('Slider')],
244
+ ['Select', c('Select')],
245
+ ['MultiSelect', c('MultiSelect')],
246
+ ['Autocomplete', c('Combobox')],
247
+ ['Combobox', c('Combobox')],
248
+ ['Modal', c('Dialog')],
249
+ ['Drawer', c('Drawer')],
250
+ ['Popover', c('Popover')],
251
+ ['Tooltip', c('Tooltip')],
252
+ ['HoverCard', c('HoverCard')],
253
+ ['Notification', c('Toast')],
254
+ ['Alert', c('Alert')],
255
+ ['Tabs', c('Tabs')],
256
+ ['Accordion', c('Accordion')],
257
+ ['Menu', c('Menu')],
258
+ ['Breadcrumbs', c('Breadcrumb')],
259
+ ['Pagination', c('Pagination')],
260
+ ['Stepper', c('Stepper')],
261
+ ['Avatar', c('Avatar')],
262
+ ['Badge', c('Badge')],
263
+ ['Chip', c('Chip')],
264
+ ['Card', c('Card')],
265
+ ['Skeleton', c('Skeleton')],
266
+ ['Loader', c('Spinner')],
267
+ ['Progress', c('Progress')],
268
+ ['Divider', c('Separator')],
269
+ ['Table', c('Table')],
270
+ ['List', c('List')],
271
+ ['Stack', c('Stack')],
272
+ ['Group', c('Stack')],
273
+ ['Grid', c('Grid')],
274
+ ['Box', c('Box')],
275
+ ['Container', c('Container')],
276
+ ['ScrollArea', c('ScrollArea')],
277
+ ['NavLink', c('NavigationMenu')],
278
+ ),
279
+ entries('@mantine/dates', ['DatePicker', c('DatePicker')], ['DateInput', c('DatePicker')], ['TimeInput', c('TimePicker')], ['Calendar', c('Calendar')]),
280
+
281
+ // ── Specialised utility libraries (§9.1 named utilities) ──────────
282
+ entries('cmdk', ['Command', c('Combobox')], ['default', c('Combobox')]),
283
+ entries('vaul', ['Drawer', c('Drawer')], ['default', c('Drawer')]),
284
+ entries('react-day-picker', ['DayPicker', c('Calendar')], ['default', c('Calendar')]),
285
+ entries('react-datepicker', ['default', c('DatePicker')]),
286
+ entries('react-modal', ['default', c('Dialog')]),
287
+ entries('react-tooltip', ['Tooltip', c('Tooltip')]),
288
+ entries('react-toastify', ['ToastContainer', c('Toast')], ['toast', c('Toast')]),
289
+ entries('react-hot-toast', ['Toaster', c('Toast')], ['default', c('Toast')]),
290
+ entries('sonner', ['Toaster', c('Toast')], ['toast', c('Toast')]),
291
+ entries('react-select', ['default', c('Select')]),
292
+ entries('downshift', ['default', c('Combobox')], ['useCombobox', c('Combobox')]),
293
+ ]);
294
+
295
+ // Quick lookup: package + imported name → canonical id (or undefined).
296
+ export function lookupLibraryImport(
297
+ pkg: string,
298
+ importedName: string,
299
+ ): CanonicalId | undefined {
300
+ return LIBRARY_MAP.get(pkg)?.get(importedName)?.canonical;
301
+ }