@schemyx/mcp 0.1.1 → 0.1.3
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/README.md +3 -0
- package/dist/codebase-scanner/bundle.d.ts +241 -7
- package/dist/codebase-scanner/bundle.js +3177 -109
- package/dist/codebase-scanner/bundle.js.map +1 -1
- package/dist/codebase-scanner/constants.d.ts +3 -3
- package/dist/codebase-scanner/constants.js +3 -2
- package/dist/codebase-scanner/constants.js.map +1 -1
- package/dist/codebase-scanner/database.d.ts +8 -0
- package/dist/codebase-scanner/database.js +1252 -0
- package/dist/codebase-scanner/database.js.map +1 -0
- package/dist/codebase-scanner/extractors.d.ts +58 -1
- package/dist/codebase-scanner/extractors.js +991 -18
- package/dist/codebase-scanner/extractors.js.map +1 -1
- package/dist/codebase-scanner/files.js +18 -1
- package/dist/codebase-scanner/files.js.map +1 -1
- package/dist/codebase-scanner/index.d.ts +2 -2
- package/dist/codebase-scanner/recipes.d.ts +9 -2
- package/dist/codebase-scanner/recipes.js +345 -1
- package/dist/codebase-scanner/recipes.js.map +1 -1
- package/dist/codebase-scanner/types.d.ts +222 -0
- package/package.json +1 -1
|
@@ -4,7 +4,15 @@ exports.createBundle = createBundle;
|
|
|
4
4
|
exports.addEntry = addEntry;
|
|
5
5
|
exports.hasStyleSurface = hasStyleSurface;
|
|
6
6
|
exports.isBackendRouteFile = isBackendRouteFile;
|
|
7
|
+
exports.hasDatabaseSurface = hasDatabaseSurface;
|
|
8
|
+
exports.addDatabaseSurfaceEntries = addDatabaseSurfaceEntries;
|
|
9
|
+
exports.databaseSurfaceText = databaseSurfaceText;
|
|
10
|
+
exports.databaseSurfaceConfidence = databaseSurfaceConfidence;
|
|
11
|
+
exports.databaseAccessDependencies = databaseAccessDependencies;
|
|
12
|
+
exports.databaseAccessEdgeType = databaseAccessEdgeType;
|
|
13
|
+
exports.databaseEnumKey = databaseEnumKey;
|
|
7
14
|
exports.backendFileContext = backendFileContext;
|
|
15
|
+
exports.databaseFileContext = databaseFileContext;
|
|
8
16
|
exports.findEndpointContract = findEndpointContract;
|
|
9
17
|
exports.createBackendEndpointFlow = createBackendEndpointFlow;
|
|
10
18
|
exports.backendCallDependencies = backendCallDependencies;
|
|
@@ -15,8 +23,99 @@ exports.sourceInfo = sourceInfo;
|
|
|
15
23
|
exports.toGraphNode = toGraphNode;
|
|
16
24
|
exports.toGraphEdge = toGraphEdge;
|
|
17
25
|
exports.addRenderEdges = addRenderEdges;
|
|
26
|
+
exports.inferStyleDefinitionVariantRoles = inferStyleDefinitionVariantRoles;
|
|
27
|
+
exports.inferActionVariantRole = inferActionVariantRole;
|
|
28
|
+
exports.createStyleDefinitionGuidance = createStyleDefinitionGuidance;
|
|
29
|
+
exports.summarizeUiElementPatterns = summarizeUiElementPatterns;
|
|
30
|
+
exports.uiElementPatternUsage = uiElementPatternUsage;
|
|
31
|
+
exports.rootUiElementsForKind = rootUiElementsForKind;
|
|
32
|
+
exports.isLayoutSafetyProfile = isLayoutSafetyProfile;
|
|
33
|
+
exports.isResponsiveProfile = isResponsiveProfile;
|
|
34
|
+
exports.isScaleProfile = isScaleProfile;
|
|
35
|
+
exports.isSemanticProfile = isSemanticProfile;
|
|
36
|
+
exports.createStyleResolutionContext = createStyleResolutionContext;
|
|
37
|
+
exports.detectProjectStyleSystems = detectProjectStyleSystems;
|
|
38
|
+
exports.classNamesFromCssSelector = classNamesFromCssSelector;
|
|
39
|
+
exports.unescapeCssIdentifier = unescapeCssIdentifier;
|
|
40
|
+
exports.cssSelectorStatePrefixes = cssSelectorStatePrefixes;
|
|
41
|
+
exports.cssRuleMediaContext = cssRuleMediaContext;
|
|
42
|
+
exports.normalizeCssDeclarationValue = normalizeCssDeclarationValue;
|
|
43
|
+
exports.cssDeclarationImportant = cssDeclarationImportant;
|
|
44
|
+
exports.estimateSelectorSpecificity = estimateSelectorSpecificity;
|
|
45
|
+
exports.resolveCssValueTokens = resolveCssValueTokens;
|
|
46
|
+
exports.resolveUiClassDeclarations = resolveUiClassDeclarations;
|
|
47
|
+
exports.tailwindDeclarationsForClass = tailwindDeclarationsForClass;
|
|
48
|
+
exports.tailwindAlignmentDeclaration = tailwindAlignmentDeclaration;
|
|
49
|
+
exports.tailwindSpacingDeclaration = tailwindSpacingDeclaration;
|
|
50
|
+
exports.tailwindSizingDeclaration = tailwindSizingDeclaration;
|
|
51
|
+
exports.tailwindTypographyDeclaration = tailwindTypographyDeclaration;
|
|
52
|
+
exports.tailwindBorderDeclaration = tailwindBorderDeclaration;
|
|
53
|
+
exports.tailwindColorDeclaration = tailwindColorDeclaration;
|
|
54
|
+
exports.tailwindColorValue = tailwindColorValue;
|
|
55
|
+
exports.cssTokenValue = cssTokenValue;
|
|
56
|
+
exports.colorKeywordValue = colorKeywordValue;
|
|
57
|
+
exports.opacityToPercent = opacityToPercent;
|
|
58
|
+
exports.tailwindScaleValue = tailwindScaleValue;
|
|
59
|
+
exports.tailwindNamedSizeValue = tailwindNamedSizeValue;
|
|
60
|
+
exports.arbitraryTokenValue = arbitraryTokenValue;
|
|
61
|
+
exports.arbitraryValue = arbitraryValue;
|
|
62
|
+
exports.looksLikeCssLength = looksLikeCssLength;
|
|
63
|
+
exports.tailwindShadowValue = tailwindShadowValue;
|
|
64
|
+
exports.bootstrapDeclarationsForClass = bootstrapDeclarationsForClass;
|
|
65
|
+
exports.isBootstrapClass = isBootstrapClass;
|
|
66
|
+
exports.bootstrapResponsivePrefixes = bootstrapResponsivePrefixes;
|
|
67
|
+
exports.stripBootstrapResponsiveInfix = stripBootstrapResponsiveInfix;
|
|
68
|
+
exports.bootstrapComponentDeclarations = bootstrapComponentDeclarations;
|
|
69
|
+
exports.bootstrapSpacingDeclarations = bootstrapSpacingDeclarations;
|
|
70
|
+
exports.bootstrapDisplayDeclarations = bootstrapDisplayDeclarations;
|
|
71
|
+
exports.bootstrapFlexDeclarations = bootstrapFlexDeclarations;
|
|
72
|
+
exports.bootstrapSizingDeclarations = bootstrapSizingDeclarations;
|
|
73
|
+
exports.bootstrapTypographyDeclarations = bootstrapTypographyDeclarations;
|
|
74
|
+
exports.bootstrapColorDeclarations = bootstrapColorDeclarations;
|
|
75
|
+
exports.bootstrapBorderDeclarations = bootstrapBorderDeclarations;
|
|
76
|
+
exports.bootstrapGridDeclarations = bootstrapGridDeclarations;
|
|
77
|
+
exports.categorizeResolvedStyleDeclarations = categorizeResolvedStyleDeclarations;
|
|
78
|
+
exports.flattenResolvedStyleCategories = flattenResolvedStyleCategories;
|
|
79
|
+
exports.createStyleSystemEvidence = createStyleSystemEvidence;
|
|
80
|
+
exports.isLikelyTailwindClass = isLikelyTailwindClass;
|
|
81
|
+
exports.resolvedStyleCategory = resolvedStyleCategory;
|
|
82
|
+
exports.mergeResolvedStyleCategories = mergeResolvedStyleCategories;
|
|
83
|
+
exports.classBucketRecord = classBucketRecord;
|
|
84
|
+
exports.resolvedStyleRecord = resolvedStyleRecord;
|
|
85
|
+
exports.createUiContract = createUiContract;
|
|
86
|
+
exports.createUiContractForElement = createUiContractForElement;
|
|
87
|
+
exports.createUiContractForChild = createUiContractForChild;
|
|
88
|
+
exports.createUiPatternContract = createUiPatternContract;
|
|
89
|
+
exports.createUiRelationships = createUiRelationships;
|
|
90
|
+
exports.createUiResponsiveVariants = createUiResponsiveVariants;
|
|
91
|
+
exports.createUiInteractionVariants = createUiInteractionVariants;
|
|
92
|
+
exports.uiContractClassBucket = uiContractClassBucket;
|
|
93
|
+
exports.mergeUiContractClassBuckets = mergeUiContractClassBuckets;
|
|
94
|
+
exports.filterUiContractClasses = filterUiContractClasses;
|
|
95
|
+
exports.isResponsivePrefix = isResponsivePrefix;
|
|
96
|
+
exports.responsivePrefixesForClass = responsivePrefixesForClass;
|
|
97
|
+
exports.isInteractionPrefix = isInteractionPrefix;
|
|
98
|
+
exports.omitEmptyRecord = omitEmptyRecord;
|
|
99
|
+
exports.mergeLayoutSafetyProfiles = mergeLayoutSafetyProfiles;
|
|
100
|
+
exports.mergeResponsiveProfiles = mergeResponsiveProfiles;
|
|
101
|
+
exports.mergeScaleProfiles = mergeScaleProfiles;
|
|
102
|
+
exports.mergeSemanticProfiles = mergeSemanticProfiles;
|
|
103
|
+
exports.summarizePatternLayoutSafety = summarizePatternLayoutSafety;
|
|
104
|
+
exports.summarizePatternResponsiveProfile = summarizePatternResponsiveProfile;
|
|
105
|
+
exports.summarizePatternScaleProfile = summarizePatternScaleProfile;
|
|
106
|
+
exports.summarizePatternSemanticProfile = summarizePatternSemanticProfile;
|
|
107
|
+
exports.summarizePatternRoleSignatures = summarizePatternRoleSignatures;
|
|
108
|
+
exports.summarizeParentContexts = summarizeParentContexts;
|
|
109
|
+
exports.summarizeElementChildContract = summarizeElementChildContract;
|
|
110
|
+
exports.summarizePatternChildStructure = summarizePatternChildStructure;
|
|
111
|
+
exports.summarizeCompoundStructure = summarizeCompoundStructure;
|
|
112
|
+
exports.createUiGenerationConstraints = createUiGenerationConstraints;
|
|
113
|
+
exports.createUiGenerationGuidance = createUiGenerationGuidance;
|
|
114
|
+
exports.summarizeActionUsages = summarizeActionUsages;
|
|
115
|
+
exports.actionVariantNamesForElement = actionVariantNamesForElement;
|
|
18
116
|
exports.addUiPatternRules = addUiPatternRules;
|
|
19
117
|
exports.addUiCompositionRules = addUiCompositionRules;
|
|
118
|
+
exports.addGlobalUiContractRules = addGlobalUiContractRules;
|
|
20
119
|
exports.addConnectedPatternRules = addConnectedPatternRules;
|
|
21
120
|
exports.buildConnectedPatternClusters = buildConnectedPatternClusters;
|
|
22
121
|
exports.createFrontendPatternInstances = createFrontendPatternInstances;
|
|
@@ -26,7 +125,17 @@ exports.isConnectedUiPatternRoot = isConnectedUiPatternRoot;
|
|
|
26
125
|
exports.normalizeConnectedChildren = normalizeConnectedChildren;
|
|
27
126
|
exports.withoutConnectedChildSignature = withoutConnectedChildSignature;
|
|
28
127
|
exports.connectedChildKind = connectedChildKind;
|
|
128
|
+
exports.isRecordValue = isRecordValue;
|
|
129
|
+
exports.isRoleSignatureRecord = isRoleSignatureRecord;
|
|
130
|
+
exports.summarizeConnectedLayoutSafety = summarizeConnectedLayoutSafety;
|
|
131
|
+
exports.summarizeConnectedResponsiveProfile = summarizeConnectedResponsiveProfile;
|
|
132
|
+
exports.summarizeConnectedScaleProfile = summarizeConnectedScaleProfile;
|
|
133
|
+
exports.summarizeConnectedSemanticProfile = summarizeConnectedSemanticProfile;
|
|
134
|
+
exports.summarizeConnectedRoleSignatures = summarizeConnectedRoleSignatures;
|
|
135
|
+
exports.summarizeConnectedCompoundStructure = summarizeConnectedCompoundStructure;
|
|
136
|
+
exports.summarizeConnectedParentContexts = summarizeConnectedParentContexts;
|
|
29
137
|
exports.summarizeConnectedChildren = summarizeConnectedChildren;
|
|
138
|
+
exports.mergeChildVariants = mergeChildVariants;
|
|
30
139
|
exports.compactConnectedPatternInstance = compactConnectedPatternInstance;
|
|
31
140
|
exports.scoreConnectedPatternGroup = scoreConnectedPatternGroup;
|
|
32
141
|
exports.connectedPatternConfidence = connectedPatternConfidence;
|
|
@@ -43,9 +152,11 @@ exports.frontendPatternWeight = frontendPatternWeight;
|
|
|
43
152
|
exports.backendPatternWeight = backendPatternWeight;
|
|
44
153
|
exports.hasBackendPatternSurface = hasBackendPatternSurface;
|
|
45
154
|
exports.backendPatternConcept = backendPatternConcept;
|
|
155
|
+
exports.databaseFieldShapeInputs = databaseFieldShapeInputs;
|
|
46
156
|
exports.summarizeModelFieldShape = summarizeModelFieldShape;
|
|
47
157
|
exports.createConnectedPatternGuidance = createConnectedPatternGuidance;
|
|
48
158
|
exports.connectedPatternSlug = connectedPatternSlug;
|
|
159
|
+
exports.roleSignatureKey = roleSignatureKey;
|
|
49
160
|
exports.classSignature = classSignature;
|
|
50
161
|
exports.normalizeLayoutRole = normalizeLayoutRole;
|
|
51
162
|
exports.normalizeTagForSignature = normalizeTagForSignature;
|
|
@@ -54,13 +165,16 @@ exports.labelSignature = labelSignature;
|
|
|
54
165
|
exports.addImportEdges = addImportEdges;
|
|
55
166
|
exports.addApiCallEdges = addApiCallEdges;
|
|
56
167
|
exports.addStyleUsageEdges = addStyleUsageEdges;
|
|
168
|
+
exports.addDatabaseUsageEdges = addDatabaseUsageEdges;
|
|
57
169
|
exports.addModelUsageEdges = addModelUsageEdges;
|
|
170
|
+
exports.addDatabaseReviewItems = addDatabaseReviewItems;
|
|
58
171
|
exports.addConceptClusters = addConceptClusters;
|
|
59
172
|
exports.applyUsedBy = applyUsedBy;
|
|
60
173
|
exports.detectProjectFacts = detectProjectFacts;
|
|
61
174
|
exports.detectFrameworkForFile = detectFrameworkForFile;
|
|
62
175
|
const backend_1 = require("./backend");
|
|
63
176
|
const constants_1 = require("./constants");
|
|
177
|
+
const database_1 = require("./database");
|
|
64
178
|
const extractors_1 = require("./extractors");
|
|
65
179
|
const utils_1 = require("./utils");
|
|
66
180
|
function createBundle(rootPath, projectType, files) {
|
|
@@ -69,6 +183,7 @@ function createBundle(rootPath, projectType, files) {
|
|
|
69
183
|
const nodes = [];
|
|
70
184
|
const edges = [];
|
|
71
185
|
const reviewItems = [];
|
|
186
|
+
const styleResolutionContext = createStyleResolutionContext(files);
|
|
72
187
|
for (const file of files) {
|
|
73
188
|
const fileSource = sourceInfo('core.files', 'high', [`path:${file.relPath}`], file);
|
|
74
189
|
addEntry(entries, usedKeys, {
|
|
@@ -112,8 +227,12 @@ function createBundle(rootPath, projectType, files) {
|
|
|
112
227
|
middleware: file.middleware,
|
|
113
228
|
jobs: file.jobs,
|
|
114
229
|
events: file.events,
|
|
115
|
-
uiElements: file.uiElements.slice(0,
|
|
230
|
+
uiElements: file.uiElements.slice(0, 80),
|
|
231
|
+
uiContracts: file.uiElements
|
|
232
|
+
.slice(0, 80)
|
|
233
|
+
.map((element) => createUiContractForElement(element, file, 'high', styleResolutionContext)),
|
|
116
234
|
...backendFileContext(file),
|
|
235
|
+
...databaseFileContext(file),
|
|
117
236
|
size: file.size,
|
|
118
237
|
sourceHash: file.sourceHash,
|
|
119
238
|
source: fileSource,
|
|
@@ -141,7 +260,7 @@ function createBundle(rootPath, projectType, files) {
|
|
|
141
260
|
nodes.push(toGraphNode(dependencyKey, 'package', dependency, file.relPath, file, 'core.dependencies'));
|
|
142
261
|
edges.push(toGraphEdge(file.key, dependencyKey, 'depends_on', 'core.dependencies', file));
|
|
143
262
|
}
|
|
144
|
-
for (const component of file.components) {
|
|
263
|
+
for (const component of (0, extractors_1.isTestFile)(file.relPath) ? [] : file.components) {
|
|
145
264
|
const componentKey = (0, utils_1.toRegistryKey)('component', component);
|
|
146
265
|
const styleKey = (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
|
|
147
266
|
const compositionRuleKey = (0, utils_1.toRegistryKey)('rule', `ui.composition.${file.relPath.replace(/\.[^.]+$/, '')}`);
|
|
@@ -171,7 +290,10 @@ function createBundle(rootPath, projectType, files) {
|
|
|
171
290
|
classExpressions: file.classExpressions.slice(0, 80),
|
|
172
291
|
componentStyleDefinitions: file.componentStyleDefinitions.slice(0, 20),
|
|
173
292
|
themeTokens: file.themeTokens.slice(0, 120),
|
|
174
|
-
uiElements: file.uiElements.slice(0,
|
|
293
|
+
uiElements: file.uiElements.slice(0, 80),
|
|
294
|
+
uiContracts: file.uiElements
|
|
295
|
+
.slice(0, 80)
|
|
296
|
+
.map((element) => createUiContractForElement(element, file, 'high', styleResolutionContext)),
|
|
175
297
|
apiCalls: file.apiCalls,
|
|
176
298
|
authHints: file.authHints,
|
|
177
299
|
kind: 'component',
|
|
@@ -208,7 +330,7 @@ function createBundle(rootPath, projectType, files) {
|
|
|
208
330
|
nodes.push(toGraphNode(hookKey, 'hook', hook, file.relPath, file, 'react'));
|
|
209
331
|
edges.push(toGraphEdge(file.key, hookKey, 'exports', 'typescript', file));
|
|
210
332
|
}
|
|
211
|
-
for (const route of file.routes) {
|
|
333
|
+
for (const route of (0, extractors_1.isTestFile)(file.relPath) ? [] : file.routes) {
|
|
212
334
|
const routeKey = (0, utils_1.toRegistryKey)('route', route);
|
|
213
335
|
const routeLayer = isBackendRouteFile(file) ? 'backend' : 'frontend';
|
|
214
336
|
const styleKey = (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
|
|
@@ -233,7 +355,10 @@ function createBundle(rootPath, projectType, files) {
|
|
|
233
355
|
route,
|
|
234
356
|
path: file.relPath,
|
|
235
357
|
kind: file.kind,
|
|
236
|
-
uiElements: file.uiElements.slice(0,
|
|
358
|
+
uiElements: file.uiElements.slice(0, 80),
|
|
359
|
+
uiContracts: file.uiElements
|
|
360
|
+
.slice(0, 80)
|
|
361
|
+
.map((element) => createUiContractForElement(element, file, 'high', styleResolutionContext)),
|
|
237
362
|
classReferences: file.classReferences.slice(0, 80),
|
|
238
363
|
tailwindUtilities: file.tailwindUtilities.slice(0, 80),
|
|
239
364
|
classExpressions: file.classExpressions.slice(0, 80),
|
|
@@ -417,31 +542,76 @@ function createBundle(rootPath, projectType, files) {
|
|
|
417
542
|
nodes.push(toGraphNode(moduleKey, 'module', moduleName, file.relPath, file, (0, extractors_1.moduleExtractorForFile)(file)));
|
|
418
543
|
edges.push(toGraphEdge(file.key, moduleKey, 'exports', (0, extractors_1.moduleExtractorForFile)(file), file));
|
|
419
544
|
}
|
|
545
|
+
if (hasDatabaseSurface(file)) {
|
|
546
|
+
addDatabaseSurfaceEntries(entries, usedKeys, nodes, edges, file);
|
|
547
|
+
}
|
|
420
548
|
for (const model of file.models) {
|
|
421
549
|
const modelKey = (0, utils_1.toRegistryKey)('model', model);
|
|
550
|
+
const databaseModel = file.databaseModels.find((candidate) => candidate.name === model);
|
|
551
|
+
const modelRelations = databaseModel?.relations ?? [];
|
|
552
|
+
const enumRefs = databaseModel?.enumRefs ?? [];
|
|
553
|
+
const modelDependencies = (0, utils_1.unique)([
|
|
554
|
+
file.key,
|
|
555
|
+
...modelRelations.map((relation) => (0, utils_1.toRegistryKey)('model', relation.toModel)),
|
|
556
|
+
...enumRefs.map((enumName) => (0, utils_1.toRegistryKey)('database', `enum.${enumName}`)),
|
|
557
|
+
]);
|
|
422
558
|
addEntry(entries, usedKeys, {
|
|
423
559
|
key: modelKey,
|
|
424
560
|
group: 'model',
|
|
425
|
-
summary: `${model} data model in ${file.relPath}.`,
|
|
426
|
-
tags:
|
|
427
|
-
|
|
561
|
+
summary: `${model} ${databaseModel?.kind ?? 'data model'} in ${file.relPath}.`,
|
|
562
|
+
tags: (0, utils_1.unique)([
|
|
563
|
+
'model',
|
|
564
|
+
'schema',
|
|
565
|
+
'database',
|
|
566
|
+
databaseModel?.kind ?? '',
|
|
567
|
+
databaseModel?.provider ?? '',
|
|
568
|
+
databaseModel?.tableName ?? '',
|
|
569
|
+
...enumRefs,
|
|
570
|
+
...modelRelations.map((relation) => relation.toModel),
|
|
571
|
+
]),
|
|
572
|
+
dependencies: modelDependencies,
|
|
428
573
|
usedBy: [],
|
|
429
|
-
confidence: 'high',
|
|
574
|
+
confidence: databaseModel?.confidence ?? 'high',
|
|
430
575
|
status: 'inferred',
|
|
431
576
|
path: file.relPath,
|
|
432
577
|
value: {
|
|
433
578
|
name: model,
|
|
434
579
|
path: file.relPath,
|
|
435
|
-
fields: file.modelFields[model] ?? [],
|
|
436
|
-
|
|
580
|
+
fields: file.modelFields[model] ?? databaseModel?.fieldNames ?? [],
|
|
581
|
+
fieldDetails: databaseModel?.fields ?? [],
|
|
582
|
+
tableName: databaseModel?.tableName,
|
|
583
|
+
modelKind: databaseModel?.kind,
|
|
584
|
+
provider: databaseModel?.provider,
|
|
585
|
+
primaryKey: databaseModel?.primaryKey ?? [],
|
|
586
|
+
uniqueFields: databaseModel?.uniqueFields ?? [],
|
|
587
|
+
indexes: databaseModel?.indexes ?? [],
|
|
588
|
+
relations: modelRelations,
|
|
589
|
+
enumRefs,
|
|
590
|
+
databaseContract: databaseModel,
|
|
591
|
+
kind: databaseModel?.kind === 'table' ? 'database-table' : 'model',
|
|
437
592
|
sourceHash: file.sourceHash,
|
|
438
|
-
source: sourceInfo((0, extractors_1.modelExtractorForFile)(file), 'high', [
|
|
593
|
+
source: sourceInfo(databaseModel ? `database.${databaseModel.provider}` : (0, extractors_1.modelExtractorForFile)(file), databaseModel?.confidence ?? 'high', [
|
|
594
|
+
`model:${model}`,
|
|
595
|
+
...(databaseModel?.tableName ? [`table:${databaseModel.tableName}`] : []),
|
|
596
|
+
], file),
|
|
439
597
|
},
|
|
440
598
|
});
|
|
441
|
-
nodes.push(toGraphNode(modelKey, 'model', model, file.relPath, file, (0, extractors_1.modelExtractorForFile)(file)
|
|
442
|
-
|
|
599
|
+
nodes.push(toGraphNode(modelKey, databaseModel?.kind === 'table' ? 'database-table' : 'model', model, file.relPath, file, databaseModel ? `database.${databaseModel.provider}` : (0, extractors_1.modelExtractorForFile)(file), databaseModel
|
|
600
|
+
? {
|
|
601
|
+
database: {
|
|
602
|
+
provider: databaseModel.provider,
|
|
603
|
+
tableName: databaseModel.tableName,
|
|
604
|
+
fields: databaseModel.fieldNames,
|
|
605
|
+
relations: databaseModel.relations.map((relation) => relation.toModel),
|
|
606
|
+
},
|
|
607
|
+
}
|
|
608
|
+
: undefined));
|
|
609
|
+
edges.push(toGraphEdge(file.key, modelKey, 'contains', databaseModel ? `database.${databaseModel.provider}` : (0, extractors_1.modelExtractorForFile)(file), file, databaseModel?.confidence ?? 'high'));
|
|
610
|
+
for (const relation of modelRelations) {
|
|
611
|
+
edges.push(toGraphEdge(modelKey, (0, utils_1.toRegistryKey)('model', relation.toModel), 'relates_to', databaseModel ? `database.${databaseModel.provider}.relations` : 'database.relations', file, relation.confidence));
|
|
612
|
+
}
|
|
443
613
|
}
|
|
444
|
-
if (hasStyleSurface(file)) {
|
|
614
|
+
if (hasStyleSurface(file) && !(0, extractors_1.isTestFile)(file.relPath)) {
|
|
445
615
|
const styleKey = (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
|
|
446
616
|
addEntry(entries, usedKeys, {
|
|
447
617
|
key: styleKey,
|
|
@@ -469,6 +639,9 @@ function createBundle(rootPath, projectType, files) {
|
|
|
469
639
|
themeTokens: file.themeTokens.slice(0, 240),
|
|
470
640
|
inlineStyles: file.inlineStyles.slice(0, 100),
|
|
471
641
|
uiElements: file.uiElements.slice(0, 80),
|
|
642
|
+
uiContracts: file.uiElements
|
|
643
|
+
.slice(0, 80)
|
|
644
|
+
.map((element) => createUiContractForElement(element, file, (0, extractors_1.styleConfidenceForFile)(file), styleResolutionContext)),
|
|
472
645
|
sourceHash: file.sourceHash,
|
|
473
646
|
source: sourceInfo((0, extractors_1.styleExtractorForFile)(file), (0, extractors_1.styleConfidenceForFile)(file), ['selectors, classes, tokens, or template UI elements'], file),
|
|
474
647
|
},
|
|
@@ -476,7 +649,7 @@ function createBundle(rootPath, projectType, files) {
|
|
|
476
649
|
nodes.push(toGraphNode(styleKey, 'style', file.relPath, file.relPath, file, (0, extractors_1.styleExtractorForFile)(file)));
|
|
477
650
|
edges.push(toGraphEdge(file.key, styleKey, 'contains', (0, extractors_1.styleExtractorForFile)(file), file, (0, extractors_1.styleConfidenceForFile)(file)));
|
|
478
651
|
}
|
|
479
|
-
for (const styleDefinition of file.componentStyleDefinitions) {
|
|
652
|
+
for (const styleDefinition of (0, extractors_1.isTestFile)(file.relPath) ? [] : file.componentStyleDefinitions) {
|
|
480
653
|
const styleDefinitionKey = (0, utils_1.toRegistryKey)('style', `${file.relPath.replace(/\.[^.]+$/, '')}.${styleDefinition.name}`);
|
|
481
654
|
addEntry(entries, usedKeys, {
|
|
482
655
|
key: styleDefinitionKey,
|
|
@@ -505,6 +678,8 @@ function createBundle(rootPath, projectType, files) {
|
|
|
505
678
|
baseClasses: styleDefinition.baseClasses,
|
|
506
679
|
variants: styleDefinition.variants,
|
|
507
680
|
defaultVariants: styleDefinition.defaultVariants,
|
|
681
|
+
variantRoles: inferStyleDefinitionVariantRoles(styleDefinition),
|
|
682
|
+
generationGuidance: createStyleDefinitionGuidance(styleDefinition),
|
|
508
683
|
classes: styleDefinition.classes,
|
|
509
684
|
sourceSnippet: styleDefinition.source,
|
|
510
685
|
sourceHash: file.sourceHash,
|
|
@@ -521,15 +696,18 @@ function createBundle(rootPath, projectType, files) {
|
|
|
521
696
|
edges.push(toGraphEdge(file.key, styleDefinitionKey, 'defines_style', 'component-style-definitions', file, 'high'));
|
|
522
697
|
}
|
|
523
698
|
}
|
|
524
|
-
addUiPatternRules(entries, usedKeys, nodes, edges, files);
|
|
525
|
-
addUiCompositionRules(entries, usedKeys, nodes, edges, files);
|
|
526
|
-
|
|
699
|
+
addUiPatternRules(entries, usedKeys, nodes, edges, files, styleResolutionContext);
|
|
700
|
+
addUiCompositionRules(entries, usedKeys, nodes, edges, files, styleResolutionContext);
|
|
701
|
+
addGlobalUiContractRules(entries, usedKeys, nodes, edges, files);
|
|
702
|
+
addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files, styleResolutionContext);
|
|
527
703
|
addRenderEdges(entries, files, edges);
|
|
528
704
|
addImportEdges(entries, files, edges, rootPath);
|
|
529
705
|
addApiCallEdges(entries, files, edges);
|
|
530
706
|
addStyleUsageEdges(entries, files, edges);
|
|
707
|
+
addDatabaseUsageEdges(entries, files, edges);
|
|
531
708
|
addModelUsageEdges(entries, files, edges);
|
|
532
709
|
addConceptClusters(entries, usedKeys, nodes, edges, reviewItems);
|
|
710
|
+
addDatabaseReviewItems(entries, reviewItems);
|
|
533
711
|
applyUsedBy(entries, edges);
|
|
534
712
|
return {
|
|
535
713
|
scanId: `${new Date().toISOString()}:${(0, utils_1.shortHash)(rootPath + entries.length)}`,
|
|
@@ -576,6 +754,215 @@ function isBackendRouteFile(file) {
|
|
|
576
754
|
file.apiHandlers.length ||
|
|
577
755
|
file.backendEndpointContracts.length);
|
|
578
756
|
}
|
|
757
|
+
function hasDatabaseSurface(file) {
|
|
758
|
+
return Boolean(file.databaseModels.length ||
|
|
759
|
+
file.databaseEnums.length ||
|
|
760
|
+
file.databaseMigrations.length ||
|
|
761
|
+
file.databaseAccesses.length);
|
|
762
|
+
}
|
|
763
|
+
function addDatabaseSurfaceEntries(entries, usedKeys, nodes, edges, file) {
|
|
764
|
+
const extractor = (0, database_1.databaseExtractorForFile)(file.relPath, databaseSurfaceText(file));
|
|
765
|
+
const confidence = databaseSurfaceConfidence(file);
|
|
766
|
+
const databaseKey = (0, utils_1.toRegistryKey)('database', file.relPath.replace(/\.[^.]+$/, ''));
|
|
767
|
+
addEntry(entries, usedKeys, {
|
|
768
|
+
key: databaseKey,
|
|
769
|
+
group: 'database',
|
|
770
|
+
summary: `Database code surface in ${file.relPath}.`,
|
|
771
|
+
tags: (0, utils_1.unique)([
|
|
772
|
+
'database',
|
|
773
|
+
'data',
|
|
774
|
+
file.language,
|
|
775
|
+
extractor,
|
|
776
|
+
...file.databaseModels.map((model) => model.provider),
|
|
777
|
+
...file.databaseModels.map((model) => model.name),
|
|
778
|
+
...file.databaseEnums.map((enumValue) => enumValue.name),
|
|
779
|
+
...file.databaseAccesses.map((access) => access.operation),
|
|
780
|
+
]),
|
|
781
|
+
dependencies: (0, utils_1.unique)([
|
|
782
|
+
file.key,
|
|
783
|
+
...file.databaseModels.map((model) => (0, utils_1.toRegistryKey)('model', model.name)),
|
|
784
|
+
...file.databaseEnums.map((enumValue) => databaseEnumKey(enumValue, file)),
|
|
785
|
+
...file.databaseMigrations.map((migration) => (0, utils_1.toRegistryKey)('database', `migration.${migration.name}`)),
|
|
786
|
+
...file.databaseAccesses.flatMap(databaseAccessDependencies),
|
|
787
|
+
]),
|
|
788
|
+
usedBy: [],
|
|
789
|
+
confidence,
|
|
790
|
+
status: 'inferred',
|
|
791
|
+
path: file.relPath,
|
|
792
|
+
value: {
|
|
793
|
+
path: file.relPath,
|
|
794
|
+
kind: 'database-surface',
|
|
795
|
+
models: file.databaseModels,
|
|
796
|
+
enums: file.databaseEnums,
|
|
797
|
+
migrations: file.databaseMigrations,
|
|
798
|
+
accesses: file.databaseAccesses,
|
|
799
|
+
sourceHash: file.sourceHash,
|
|
800
|
+
source: sourceInfo(extractor, confidence, ['database models, enums, migrations, or code access patterns'], file),
|
|
801
|
+
},
|
|
802
|
+
});
|
|
803
|
+
nodes.push(toGraphNode(databaseKey, 'database', file.relPath, file.relPath, file, extractor));
|
|
804
|
+
edges.push(toGraphEdge(file.key, databaseKey, 'contains', 'database.surface', file, confidence));
|
|
805
|
+
for (const enumValue of file.databaseEnums) {
|
|
806
|
+
const enumKey = databaseEnumKey(enumValue, file);
|
|
807
|
+
addEntry(entries, usedKeys, {
|
|
808
|
+
key: enumKey,
|
|
809
|
+
group: 'database',
|
|
810
|
+
summary: `${enumValue.name} database enum with ${enumValue.values.length} values.`,
|
|
811
|
+
tags: (0, utils_1.unique)(['database', 'enum', enumValue.provider, enumValue.name, ...enumValue.values]),
|
|
812
|
+
dependencies: [file.key],
|
|
813
|
+
usedBy: [],
|
|
814
|
+
confidence: enumValue.confidence,
|
|
815
|
+
status: 'inferred',
|
|
816
|
+
path: file.relPath,
|
|
817
|
+
value: {
|
|
818
|
+
...enumValue,
|
|
819
|
+
kind: 'database-enum',
|
|
820
|
+
sourceHash: file.sourceHash,
|
|
821
|
+
source: sourceInfo(`database.${enumValue.provider}`, enumValue.confidence, [`enum:${enumValue.name}`, `values:${enumValue.values.join(',')}`], file),
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
nodes.push(toGraphNode(enumKey, 'database-enum', enumValue.name, file.relPath, file, `database.${enumValue.provider}`, {
|
|
825
|
+
enum: { values: enumValue.values },
|
|
826
|
+
}));
|
|
827
|
+
edges.push(toGraphEdge(file.key, enumKey, 'defines_enum', `database.${enumValue.provider}`, file));
|
|
828
|
+
}
|
|
829
|
+
for (const migration of file.databaseMigrations) {
|
|
830
|
+
const migrationKey = (0, utils_1.toRegistryKey)('database', `migration.${migration.name}`);
|
|
831
|
+
const migrationDependencies = (0, utils_1.unique)([
|
|
832
|
+
file.key,
|
|
833
|
+
...migration.operations
|
|
834
|
+
.map((operation) => operation.target ?? '')
|
|
835
|
+
.filter(Boolean)
|
|
836
|
+
.map((target) => (0, utils_1.toRegistryKey)('model', target)),
|
|
837
|
+
]);
|
|
838
|
+
addEntry(entries, usedKeys, {
|
|
839
|
+
key: migrationKey,
|
|
840
|
+
group: 'database',
|
|
841
|
+
summary: `${migration.name} database migration: ${migration.operations.length} operations.`,
|
|
842
|
+
tags: (0, utils_1.unique)([
|
|
843
|
+
'database',
|
|
844
|
+
'migration',
|
|
845
|
+
migration.provider,
|
|
846
|
+
...(migration.destructive ? ['destructive'] : []),
|
|
847
|
+
...(migration.seedLike ? ['seed-like'] : []),
|
|
848
|
+
...migration.creates.map((item) => `creates:${item}`),
|
|
849
|
+
...migration.alters.map((item) => `alters:${item}`),
|
|
850
|
+
...migration.drops.map((item) => `drops:${item}`),
|
|
851
|
+
]),
|
|
852
|
+
dependencies: migrationDependencies,
|
|
853
|
+
usedBy: [],
|
|
854
|
+
confidence: migration.confidence,
|
|
855
|
+
status: migration.destructive ? 'needs_review' : 'inferred',
|
|
856
|
+
path: file.relPath,
|
|
857
|
+
value: {
|
|
858
|
+
...migration,
|
|
859
|
+
kind: 'database-migration',
|
|
860
|
+
sourceHash: file.sourceHash,
|
|
861
|
+
source: sourceInfo(`database.${migration.provider}.migration`, migration.confidence, [`migration:${migration.name}`, `operations:${migration.operations.length}`], file),
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
nodes.push(toGraphNode(migrationKey, 'database-migration', migration.name, file.relPath, file, `database.${migration.provider}.migration`, {
|
|
865
|
+
migration: {
|
|
866
|
+
operations: migration.operations.length,
|
|
867
|
+
destructive: migration.destructive,
|
|
868
|
+
seedLike: migration.seedLike,
|
|
869
|
+
},
|
|
870
|
+
}));
|
|
871
|
+
edges.push(toGraphEdge(file.key, migrationKey, 'contains', `database.${migration.provider}.migration`, file, migration.confidence));
|
|
872
|
+
for (const dependency of migrationDependencies.filter((key) => key.startsWith('model.'))) {
|
|
873
|
+
edges.push(toGraphEdge(migrationKey, dependency, 'changes_schema', `database.${migration.provider}.migration`, file, migration.confidence));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
for (const access of file.databaseAccesses) {
|
|
877
|
+
const accessKey = (0, utils_1.toRegistryKey)('database', `access.${file.relPath.replace(/\.[^.]+$/, '')}.${access.line}.${access.operation}.${access.model ?? access.table ?? access.receiver ?? 'unknown'}`);
|
|
878
|
+
const dependencies = (0, utils_1.unique)([file.key, ...databaseAccessDependencies(access)]);
|
|
879
|
+
addEntry(entries, usedKeys, {
|
|
880
|
+
key: accessKey,
|
|
881
|
+
group: 'database',
|
|
882
|
+
summary: `${(0, utils_1.titleCase)(access.operation)} database access${access.model || access.table ? ` for ${access.model ?? access.table}` : ''} in ${file.relPath}.`,
|
|
883
|
+
tags: (0, utils_1.unique)([
|
|
884
|
+
'database',
|
|
885
|
+
'data-access',
|
|
886
|
+
access.framework,
|
|
887
|
+
access.operation,
|
|
888
|
+
access.model ?? '',
|
|
889
|
+
access.table ?? '',
|
|
890
|
+
...(access.raw ? ['raw-sql'] : []),
|
|
891
|
+
...(access.transactional ? ['transaction'] : []),
|
|
892
|
+
...access.risk,
|
|
893
|
+
]),
|
|
894
|
+
dependencies,
|
|
895
|
+
usedBy: [],
|
|
896
|
+
confidence: access.confidence,
|
|
897
|
+
status: access.risk.includes('manual-review') ? 'needs_review' : 'inferred',
|
|
898
|
+
path: file.relPath,
|
|
899
|
+
value: {
|
|
900
|
+
...access,
|
|
901
|
+
kind: 'database-access',
|
|
902
|
+
targetKeys: databaseAccessDependencies(access),
|
|
903
|
+
sourceHash: file.sourceHash,
|
|
904
|
+
source: sourceInfo(`database.${access.framework}.access`, access.confidence, [`${access.operation}:${access.model ?? access.table ?? access.receiver ?? 'unknown'}`], file),
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
nodes.push(toGraphNode(accessKey, 'database-access', `${access.operation}:${access.model ?? access.table ?? 'unknown'}`, file.relPath, file, `database.${access.framework}.access`, {
|
|
908
|
+
access: {
|
|
909
|
+
operation: access.operation,
|
|
910
|
+
model: access.model,
|
|
911
|
+
table: access.table,
|
|
912
|
+
risk: access.risk,
|
|
913
|
+
},
|
|
914
|
+
}));
|
|
915
|
+
edges.push(toGraphEdge(file.key, accessKey, 'contains', `database.${access.framework}.access`, file, access.confidence));
|
|
916
|
+
for (const dependency of dependencies.filter((key) => key.startsWith('model.'))) {
|
|
917
|
+
const edgeType = databaseAccessEdgeType(access);
|
|
918
|
+
edges.push(toGraphEdge(accessKey, dependency, edgeType, `database.${access.framework}.access`, file, access.confidence));
|
|
919
|
+
edges.push(toGraphEdge(file.key, dependency, edgeType, `database.${access.framework}.access`, file, access.confidence));
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function databaseSurfaceText(file) {
|
|
924
|
+
return [
|
|
925
|
+
file.relPath,
|
|
926
|
+
file.language,
|
|
927
|
+
...file.models,
|
|
928
|
+
...file.databaseModels.map((model) => `${model.provider}:${model.name}:${model.tableName ?? ''}`),
|
|
929
|
+
...file.databaseEnums.map((enumValue) => `enum:${enumValue.name}`),
|
|
930
|
+
...file.databaseMigrations.map((migration) => `migration:${migration.name}`),
|
|
931
|
+
...file.databaseAccesses.map((access) => `${access.framework}:${access.operation}:${access.model ?? access.table ?? ''}`),
|
|
932
|
+
].join('\n');
|
|
933
|
+
}
|
|
934
|
+
function databaseSurfaceConfidence(file) {
|
|
935
|
+
if (file.databaseModels.some((model) => model.confidence === 'high') ||
|
|
936
|
+
file.databaseEnums.length) {
|
|
937
|
+
return 'high';
|
|
938
|
+
}
|
|
939
|
+
if (file.databaseMigrations.length ||
|
|
940
|
+
file.databaseAccesses.some((access) => access.confidence === 'medium')) {
|
|
941
|
+
return 'medium';
|
|
942
|
+
}
|
|
943
|
+
return 'low';
|
|
944
|
+
}
|
|
945
|
+
function databaseAccessDependencies(access) {
|
|
946
|
+
return (0, utils_1.unique)([
|
|
947
|
+
access.model ? (0, utils_1.toRegistryKey)('model', access.model) : '',
|
|
948
|
+
access.table ? (0, utils_1.toRegistryKey)('model', access.table) : '',
|
|
949
|
+
]);
|
|
950
|
+
}
|
|
951
|
+
function databaseAccessEdgeType(access) {
|
|
952
|
+
if (['create', 'update', 'delete', 'upsert', 'write', 'raw-sql'].includes(access.operation)) {
|
|
953
|
+
return 'writes';
|
|
954
|
+
}
|
|
955
|
+
if (access.operation === 'transaction') {
|
|
956
|
+
return 'transaction_scope';
|
|
957
|
+
}
|
|
958
|
+
return 'reads';
|
|
959
|
+
}
|
|
960
|
+
function databaseEnumKey(enumValue, file) {
|
|
961
|
+
if (enumValue.provider === 'prisma' && /schema\.prisma$/i.test(file.relPath)) {
|
|
962
|
+
return (0, utils_1.toRegistryKey)('database', `enum.${enumValue.name}`);
|
|
963
|
+
}
|
|
964
|
+
return (0, utils_1.toRegistryKey)('database', `enum.${enumValue.provider}.${enumValue.name}.${(0, utils_1.shortHash)(`${file.relPath}:${enumValue.values.join('|')}`)}`);
|
|
965
|
+
}
|
|
579
966
|
function backendFileContext(file) {
|
|
580
967
|
if (!file.backendEndpointContracts.length &&
|
|
581
968
|
!file.backendOperations.length &&
|
|
@@ -594,6 +981,19 @@ function backendFileContext(file) {
|
|
|
594
981
|
},
|
|
595
982
|
};
|
|
596
983
|
}
|
|
984
|
+
function databaseFileContext(file) {
|
|
985
|
+
if (!hasDatabaseSurface(file)) {
|
|
986
|
+
return {};
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
database: {
|
|
990
|
+
models: file.databaseModels,
|
|
991
|
+
enums: file.databaseEnums,
|
|
992
|
+
migrations: file.databaseMigrations,
|
|
993
|
+
accesses: file.databaseAccesses,
|
|
994
|
+
},
|
|
995
|
+
};
|
|
996
|
+
}
|
|
597
997
|
function findEndpointContract(file, handler) {
|
|
598
998
|
const [method, ...pathParts] = handler.split(/\s+/);
|
|
599
999
|
const handlerPath = pathParts.join(' ');
|
|
@@ -646,79 +1046,1923 @@ function backendCallDependencies(calls) {
|
|
|
646
1046
|
.filter((call) => ['service', 'repository', 'provider', 'client'].includes(call.kind))
|
|
647
1047
|
.map(backendCallKey));
|
|
648
1048
|
}
|
|
649
|
-
function addBackendCallEdges(fromKey, calls, file, edges) {
|
|
650
|
-
for (const call of calls) {
|
|
651
|
-
const targetKey = backendCallKey(call);
|
|
652
|
-
edges.push({
|
|
653
|
-
from: fromKey,
|
|
654
|
-
to: targetKey,
|
|
655
|
-
type: call.kind === 'repository' ? 'calls_repository' : 'calls',
|
|
656
|
-
confidence: call.kind === 'unknown' ? 'low' : 'medium',
|
|
657
|
-
source: sourceInfo('backend.contract.calls', call.kind === 'unknown' ? 'low' : 'medium', [`${fromKey} calls ${call.receiver}.${call.method}`], file),
|
|
658
|
-
});
|
|
1049
|
+
function addBackendCallEdges(fromKey, calls, file, edges) {
|
|
1050
|
+
for (const call of calls) {
|
|
1051
|
+
const targetKey = backendCallKey(call);
|
|
1052
|
+
edges.push({
|
|
1053
|
+
from: fromKey,
|
|
1054
|
+
to: targetKey,
|
|
1055
|
+
type: call.kind === 'repository' ? 'calls_repository' : 'calls',
|
|
1056
|
+
confidence: call.kind === 'unknown' ? 'low' : 'medium',
|
|
1057
|
+
source: sourceInfo('backend.contract.calls', call.kind === 'unknown' ? 'low' : 'medium', [`${fromKey} calls ${call.receiver}.${call.method}`], file),
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function backendCallKey(call) {
|
|
1062
|
+
return (0, utils_1.toRegistryKey)('service', (0, backend_1.backendReceiverToClassName)(call.receiver));
|
|
1063
|
+
}
|
|
1064
|
+
function uniqueSideEffectsForRecipe(sideEffects) {
|
|
1065
|
+
return (0, utils_1.uniqueBy)(sideEffects, (sideEffect) => `${sideEffect.kind}.${sideEffect.target ?? ''}.${sideEffect.operation ?? ''}.${sideEffect.line}`);
|
|
1066
|
+
}
|
|
1067
|
+
function sourceInfo(extractor, confidence, evidence, file) {
|
|
1068
|
+
return {
|
|
1069
|
+
extractor,
|
|
1070
|
+
confidence,
|
|
1071
|
+
evidence,
|
|
1072
|
+
path: file.relPath,
|
|
1073
|
+
hash: file.sourceHash,
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function toGraphNode(key, kind, name, relPath, file, extractor, facets) {
|
|
1077
|
+
return {
|
|
1078
|
+
k: key,
|
|
1079
|
+
kind,
|
|
1080
|
+
name,
|
|
1081
|
+
path: relPath,
|
|
1082
|
+
language: file.language,
|
|
1083
|
+
framework: detectFrameworkForFile(file),
|
|
1084
|
+
...(facets ? { facets } : {}),
|
|
1085
|
+
source: sourceInfo(extractor, 'high', [`path:${relPath}`], file),
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function toGraphEdge(from, to, type, extractor, file, confidence = 'high') {
|
|
1089
|
+
return {
|
|
1090
|
+
from,
|
|
1091
|
+
to,
|
|
1092
|
+
type,
|
|
1093
|
+
confidence,
|
|
1094
|
+
source: sourceInfo(extractor, confidence, [`${from} ${type} ${to}`], file),
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function addRenderEdges(entries, files, edges) {
|
|
1098
|
+
const componentKeysByName = new Map();
|
|
1099
|
+
for (const entry of entries.filter((item) => item.group === 'component')) {
|
|
1100
|
+
const name = typeof entry.value.name === 'string' ? entry.value.name : undefined;
|
|
1101
|
+
if (name) {
|
|
1102
|
+
componentKeysByName.set(name, entry.key);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
for (const file of files) {
|
|
1106
|
+
if (!file.routes.length || !file.jsxElements.length) {
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
for (const route of file.routes) {
|
|
1110
|
+
const routeKey = (0, utils_1.toRegistryKey)('route', route);
|
|
1111
|
+
for (const jsxElement of file.jsxElements) {
|
|
1112
|
+
const componentKey = componentKeysByName.get(jsxElement);
|
|
1113
|
+
if (componentKey) {
|
|
1114
|
+
edges.push(toGraphEdge(routeKey, componentKey, 'renders', 'react-jsx', file));
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
function inferStyleDefinitionVariantRoles(styleDefinition) {
|
|
1121
|
+
const roles = {};
|
|
1122
|
+
for (const [groupName, variants] of Object.entries(styleDefinition.variants)) {
|
|
1123
|
+
if (!/^(?:variant|intent|tone|appearance|color)$/i.test(groupName)) {
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
roles[groupName] = Object.fromEntries(Object.entries(variants).map(([variantName, classes]) => [
|
|
1127
|
+
variantName,
|
|
1128
|
+
inferActionVariantRole(variantName, classes),
|
|
1129
|
+
]));
|
|
1130
|
+
}
|
|
1131
|
+
return roles;
|
|
1132
|
+
}
|
|
1133
|
+
function inferActionVariantRole(variantName, classes) {
|
|
1134
|
+
const name = variantName.toLowerCase();
|
|
1135
|
+
const classText = classes.join(' ').toLowerCase();
|
|
1136
|
+
const isGhostLike = /^(?:ghost|link|quiet|plain|unstyled|transparent)$/.test(name) ||
|
|
1137
|
+
(classText.includes('border-transparent') && classText.includes('bg-transparent')) ||
|
|
1138
|
+
classText.includes('rounded-none');
|
|
1139
|
+
const isPrimaryLike = /^(?:default|primary|solid|main)$/.test(name) ||
|
|
1140
|
+
classText.includes('bg-primary') ||
|
|
1141
|
+
classText.includes('text-primary-foreground');
|
|
1142
|
+
const isSecondaryLike = /^(?:secondary|subtle)$/.test(name) ||
|
|
1143
|
+
classText.includes('bg-secondary') ||
|
|
1144
|
+
classText.includes('text-secondary-foreground');
|
|
1145
|
+
const isOutlineLike = /^(?:outline|bordered)$/.test(name) ||
|
|
1146
|
+
(classText.includes('border-border') && classText.includes('bg-transparent'));
|
|
1147
|
+
if (isGhostLike) {
|
|
1148
|
+
return {
|
|
1149
|
+
visualRole: name === 'link' ? 'inline-link' : 'quiet-action',
|
|
1150
|
+
generationRole: 'low-emphasis chrome, navigation, icon, or inline action',
|
|
1151
|
+
ctaSafe: false,
|
|
1152
|
+
emphasis: 'low',
|
|
1153
|
+
avoidFor: ['primary CTA', 'card footer CTA', 'form submit', 'sales/beta conversion'],
|
|
1154
|
+
reason: 'Transparent or link-style variants can look like plain text until hover.',
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
if (isPrimaryLike) {
|
|
1158
|
+
return {
|
|
1159
|
+
visualRole: 'primary-action',
|
|
1160
|
+
generationRole: 'main CTA or form submit',
|
|
1161
|
+
ctaSafe: true,
|
|
1162
|
+
emphasis: 'high',
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
if (isSecondaryLike) {
|
|
1166
|
+
return {
|
|
1167
|
+
visualRole: 'secondary-action',
|
|
1168
|
+
generationRole: 'secondary CTA or alternate action',
|
|
1169
|
+
ctaSafe: true,
|
|
1170
|
+
emphasis: 'medium',
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
if (isOutlineLike) {
|
|
1174
|
+
return {
|
|
1175
|
+
visualRole: 'outlined-action',
|
|
1176
|
+
generationRole: 'secondary CTA when a visible border is desired',
|
|
1177
|
+
ctaSafe: true,
|
|
1178
|
+
emphasis: 'medium',
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
return {
|
|
1182
|
+
visualRole: 'custom-action',
|
|
1183
|
+
generationRole: 'use only when the task names this variant or matching source context',
|
|
1184
|
+
ctaSafe: false,
|
|
1185
|
+
emphasis: 'unknown',
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
function createStyleDefinitionGuidance(styleDefinition) {
|
|
1189
|
+
const guidance = [
|
|
1190
|
+
'When generating actions, combine baseClasses with the selected variant and size classes; do not use variant classes alone.',
|
|
1191
|
+
];
|
|
1192
|
+
const variantRoles = inferStyleDefinitionVariantRoles(styleDefinition);
|
|
1193
|
+
const variantValues = Object.values(variantRoles).flatMap((group) => Object.entries(group));
|
|
1194
|
+
const hasQuietVariant = variantValues.some(([, role]) => role.ctaSafe === false);
|
|
1195
|
+
const hasPrimaryVariant = variantValues.some(([, role]) => role.visualRole === 'primary-action');
|
|
1196
|
+
if (hasPrimaryVariant) {
|
|
1197
|
+
guidance.push('Use primary/default action variants for main conversion CTAs, submit buttons, and card footer actions unless the source pattern says otherwise.');
|
|
1198
|
+
}
|
|
1199
|
+
if (hasQuietVariant) {
|
|
1200
|
+
guidance.push('Do not use ghost/link/quiet variants for primary CTAs; those variants are low-emphasis and may render as text until hover.');
|
|
1201
|
+
}
|
|
1202
|
+
if (Object.keys(styleDefinition.defaultVariants).length) {
|
|
1203
|
+
guidance.push(`Default variants: ${Object.entries(styleDefinition.defaultVariants)
|
|
1204
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
1205
|
+
.join(', ')}.`);
|
|
1206
|
+
}
|
|
1207
|
+
return guidance;
|
|
1208
|
+
}
|
|
1209
|
+
function summarizeUiElementPatterns(elements, styleContext) {
|
|
1210
|
+
return elements.slice(0, 80).map((element) => {
|
|
1211
|
+
const childStructureContract = element.childSummary?.length
|
|
1212
|
+
? summarizeElementChildContract(element.childSummary, styleContext)
|
|
1213
|
+
: undefined;
|
|
1214
|
+
const compoundStructure = element.semanticProfile?.isCompound
|
|
1215
|
+
? summarizeCompoundStructure([element], styleContext)
|
|
1216
|
+
: undefined;
|
|
1217
|
+
return {
|
|
1218
|
+
usage: uiElementPatternUsage(element),
|
|
1219
|
+
kind: element.kind,
|
|
1220
|
+
tag: element.originalTag,
|
|
1221
|
+
...(element.layoutRole ? { layoutRole: element.layoutRole } : {}),
|
|
1222
|
+
...(element.parentKind ? { parentKind: element.parentKind } : {}),
|
|
1223
|
+
...(element.label ? { label: element.label } : {}),
|
|
1224
|
+
line: element.line,
|
|
1225
|
+
uiContract: createUiContractForElement(element, undefined, undefined, styleContext),
|
|
1226
|
+
relationships: createUiRelationships({
|
|
1227
|
+
parentContext: summarizeParentContexts([element])[0],
|
|
1228
|
+
childStructureContract,
|
|
1229
|
+
compoundStructure,
|
|
1230
|
+
}),
|
|
1231
|
+
defaultClasses: element.defaultClasses ?? [],
|
|
1232
|
+
conditionalClasses: element.conditionalClasses ?? [],
|
|
1233
|
+
decorativeAccentClasses: element.decorativeAccentClasses ?? [],
|
|
1234
|
+
classGroups: element.classGroups ?? {},
|
|
1235
|
+
...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
|
|
1236
|
+
...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
|
|
1237
|
+
...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
|
|
1238
|
+
...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
|
|
1239
|
+
...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
|
|
1240
|
+
...(childStructureContract ? { childStructure: childStructureContract } : {}),
|
|
1241
|
+
...(element.variants ? { variants: element.variants } : {}),
|
|
1242
|
+
...(element.styleHelper ? { styleHelper: element.styleHelper } : {}),
|
|
1243
|
+
...(element.styleHelperVariants ? { styleHelperVariants: element.styleHelperVariants } : {}),
|
|
1244
|
+
};
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
function uiElementPatternUsage(element) {
|
|
1248
|
+
if (element.layoutRole?.includes('slot')) {
|
|
1249
|
+
return 'slot';
|
|
1250
|
+
}
|
|
1251
|
+
if (!element.parentKind || element.parentKind !== element.kind) {
|
|
1252
|
+
return 'root';
|
|
1253
|
+
}
|
|
1254
|
+
return 'nested';
|
|
1255
|
+
}
|
|
1256
|
+
function rootUiElementsForKind(elements) {
|
|
1257
|
+
const rootElements = elements.filter((element) => uiElementPatternUsage(element) === 'root');
|
|
1258
|
+
const depthValues = rootElements.map((element) => element.depth ?? 0);
|
|
1259
|
+
const minimumDepth = Math.min(...depthValues);
|
|
1260
|
+
return rootElements.filter((element) => (element.depth ?? 0) === minimumDepth);
|
|
1261
|
+
}
|
|
1262
|
+
function isLayoutSafetyProfile(value) {
|
|
1263
|
+
return Boolean(value &&
|
|
1264
|
+
typeof value === 'object' &&
|
|
1265
|
+
Array.isArray(value.layoutSafetyClasses));
|
|
1266
|
+
}
|
|
1267
|
+
function isResponsiveProfile(value) {
|
|
1268
|
+
return Boolean(value &&
|
|
1269
|
+
typeof value === 'object' &&
|
|
1270
|
+
typeof value.breakpoints === 'object' &&
|
|
1271
|
+
!Array.isArray(value.breakpoints));
|
|
1272
|
+
}
|
|
1273
|
+
function isScaleProfile(value) {
|
|
1274
|
+
return Boolean(value &&
|
|
1275
|
+
typeof value === 'object' &&
|
|
1276
|
+
Array.isArray(value.typographyClasses));
|
|
1277
|
+
}
|
|
1278
|
+
function isSemanticProfile(value) {
|
|
1279
|
+
return Boolean(value && typeof value === 'object' && Array.isArray(value.roles));
|
|
1280
|
+
}
|
|
1281
|
+
const tailwindSpacingScale = {
|
|
1282
|
+
'0': '0px',
|
|
1283
|
+
px: '1px',
|
|
1284
|
+
'0.5': '0.125rem',
|
|
1285
|
+
'1': '0.25rem',
|
|
1286
|
+
'1.5': '0.375rem',
|
|
1287
|
+
'2': '0.5rem',
|
|
1288
|
+
'2.5': '0.625rem',
|
|
1289
|
+
'3': '0.75rem',
|
|
1290
|
+
'3.5': '0.875rem',
|
|
1291
|
+
'4': '1rem',
|
|
1292
|
+
'5': '1.25rem',
|
|
1293
|
+
'6': '1.5rem',
|
|
1294
|
+
'7': '1.75rem',
|
|
1295
|
+
'8': '2rem',
|
|
1296
|
+
'9': '2.25rem',
|
|
1297
|
+
'10': '2.5rem',
|
|
1298
|
+
'11': '2.75rem',
|
|
1299
|
+
'12': '3rem',
|
|
1300
|
+
'14': '3.5rem',
|
|
1301
|
+
'16': '4rem',
|
|
1302
|
+
'20': '5rem',
|
|
1303
|
+
'24': '6rem',
|
|
1304
|
+
'28': '7rem',
|
|
1305
|
+
'32': '8rem',
|
|
1306
|
+
'36': '9rem',
|
|
1307
|
+
'40': '10rem',
|
|
1308
|
+
'44': '11rem',
|
|
1309
|
+
'48': '12rem',
|
|
1310
|
+
'52': '13rem',
|
|
1311
|
+
'56': '14rem',
|
|
1312
|
+
'60': '15rem',
|
|
1313
|
+
'64': '16rem',
|
|
1314
|
+
'72': '18rem',
|
|
1315
|
+
'80': '20rem',
|
|
1316
|
+
'96': '24rem',
|
|
1317
|
+
};
|
|
1318
|
+
const tailwindFontSizeScale = {
|
|
1319
|
+
xs: '0.75rem',
|
|
1320
|
+
sm: '0.875rem',
|
|
1321
|
+
base: '1rem',
|
|
1322
|
+
lg: '1.125rem',
|
|
1323
|
+
xl: '1.25rem',
|
|
1324
|
+
'2xl': '1.5rem',
|
|
1325
|
+
'3xl': '1.875rem',
|
|
1326
|
+
'4xl': '2.25rem',
|
|
1327
|
+
'5xl': '3rem',
|
|
1328
|
+
'6xl': '3.75rem',
|
|
1329
|
+
'7xl': '4.5rem',
|
|
1330
|
+
'8xl': '6rem',
|
|
1331
|
+
'9xl': '8rem',
|
|
1332
|
+
};
|
|
1333
|
+
const tailwindFontWeightScale = {
|
|
1334
|
+
thin: '100',
|
|
1335
|
+
extralight: '200',
|
|
1336
|
+
light: '300',
|
|
1337
|
+
normal: '400',
|
|
1338
|
+
medium: '500',
|
|
1339
|
+
semibold: '600',
|
|
1340
|
+
bold: '700',
|
|
1341
|
+
extrabold: '800',
|
|
1342
|
+
black: '900',
|
|
1343
|
+
};
|
|
1344
|
+
const tailwindLineHeightScale = {
|
|
1345
|
+
none: '1',
|
|
1346
|
+
tight: '1.25',
|
|
1347
|
+
snug: '1.375',
|
|
1348
|
+
normal: '1.5',
|
|
1349
|
+
relaxed: '1.625',
|
|
1350
|
+
loose: '2',
|
|
1351
|
+
};
|
|
1352
|
+
const tailwindTrackingScale = {
|
|
1353
|
+
tighter: '-0.05em',
|
|
1354
|
+
tight: '-0.025em',
|
|
1355
|
+
normal: '0em',
|
|
1356
|
+
wide: '0.025em',
|
|
1357
|
+
wider: '0.05em',
|
|
1358
|
+
widest: '0.1em',
|
|
1359
|
+
};
|
|
1360
|
+
const tailwindRadiusScale = {
|
|
1361
|
+
none: '0px',
|
|
1362
|
+
sm: '0.125rem',
|
|
1363
|
+
DEFAULT: '0.25rem',
|
|
1364
|
+
md: '0.375rem',
|
|
1365
|
+
lg: '0.5rem',
|
|
1366
|
+
xl: '0.75rem',
|
|
1367
|
+
'2xl': '1rem',
|
|
1368
|
+
'3xl': '1.5rem',
|
|
1369
|
+
full: '9999px',
|
|
1370
|
+
};
|
|
1371
|
+
const bootstrapSpacingScale = {
|
|
1372
|
+
'0': '0',
|
|
1373
|
+
'1': '0.25rem',
|
|
1374
|
+
'2': '0.5rem',
|
|
1375
|
+
'3': '1rem',
|
|
1376
|
+
'4': '1.5rem',
|
|
1377
|
+
'5': '3rem',
|
|
1378
|
+
auto: 'auto',
|
|
1379
|
+
};
|
|
1380
|
+
const bootstrapFontSizeScale = {
|
|
1381
|
+
'1': '2.5rem',
|
|
1382
|
+
'2': '2rem',
|
|
1383
|
+
'3': '1.75rem',
|
|
1384
|
+
'4': '1.5rem',
|
|
1385
|
+
'5': '1.25rem',
|
|
1386
|
+
'6': '1rem',
|
|
1387
|
+
};
|
|
1388
|
+
const bootstrapThemeColors = {
|
|
1389
|
+
primary: 'var(--bs-primary)',
|
|
1390
|
+
secondary: 'var(--bs-secondary)',
|
|
1391
|
+
success: 'var(--bs-success)',
|
|
1392
|
+
danger: 'var(--bs-danger)',
|
|
1393
|
+
warning: 'var(--bs-warning)',
|
|
1394
|
+
info: 'var(--bs-info)',
|
|
1395
|
+
light: 'var(--bs-light)',
|
|
1396
|
+
dark: 'var(--bs-dark)',
|
|
1397
|
+
body: 'var(--bs-body-color)',
|
|
1398
|
+
muted: 'var(--bs-secondary-color)',
|
|
1399
|
+
white: '#fff',
|
|
1400
|
+
black: '#000',
|
|
1401
|
+
};
|
|
1402
|
+
function createStyleResolutionContext(files) {
|
|
1403
|
+
const classRules = new Map();
|
|
1404
|
+
const cssVariables = new Map();
|
|
1405
|
+
const detectedStyleSystems = detectProjectStyleSystems(files);
|
|
1406
|
+
let ruleOrder = 0;
|
|
1407
|
+
for (const file of files) {
|
|
1408
|
+
for (const variable of file.cssVariableValues) {
|
|
1409
|
+
const values = cssVariables.get(variable.name) ?? [];
|
|
1410
|
+
values.push({
|
|
1411
|
+
value: variable.value,
|
|
1412
|
+
selector: variable.selector,
|
|
1413
|
+
path: file.relPath,
|
|
1414
|
+
line: variable.line,
|
|
1415
|
+
});
|
|
1416
|
+
cssVariables.set(variable.name, values);
|
|
1417
|
+
}
|
|
1418
|
+
for (const rule of file.cssRules) {
|
|
1419
|
+
const classNames = classNamesFromCssSelector(rule.selector);
|
|
1420
|
+
ruleOrder += 1;
|
|
1421
|
+
for (const className of classNames) {
|
|
1422
|
+
const declarations = rule.declarations.map((declaration) => ({
|
|
1423
|
+
property: declaration.property,
|
|
1424
|
+
value: normalizeCssDeclarationValue(declaration.value),
|
|
1425
|
+
resolvedValue: resolveCssValueTokens(declaration.value, cssVariables),
|
|
1426
|
+
source: 'css-rule',
|
|
1427
|
+
styleSystem: 'raw-css',
|
|
1428
|
+
className,
|
|
1429
|
+
selector: rule.selector,
|
|
1430
|
+
path: file.relPath,
|
|
1431
|
+
line: rule.line,
|
|
1432
|
+
prefixes: cssSelectorStatePrefixes(rule.selector),
|
|
1433
|
+
media: rule.media ?? cssRuleMediaContext(rule.selector),
|
|
1434
|
+
specificity: estimateSelectorSpecificity(rule.selector),
|
|
1435
|
+
ruleOrder,
|
|
1436
|
+
cascadeRank: estimateSelectorSpecificity(rule.selector) * 10000 +
|
|
1437
|
+
ruleOrder +
|
|
1438
|
+
(cssDeclarationImportant(declaration.value) ? 100000000 : 0),
|
|
1439
|
+
important: cssDeclarationImportant(declaration.value),
|
|
1440
|
+
}));
|
|
1441
|
+
classRules.set(className, [
|
|
1442
|
+
...(classRules.get(className) ?? []),
|
|
1443
|
+
...declarations,
|
|
1444
|
+
].slice(-320));
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return { classRules, cssVariables, detectedStyleSystems };
|
|
1449
|
+
}
|
|
1450
|
+
function detectProjectStyleSystems(files) {
|
|
1451
|
+
const systems = new Set();
|
|
1452
|
+
const allClasses = (0, utils_1.unique)(files.flatMap((file) => [
|
|
1453
|
+
...file.classReferences,
|
|
1454
|
+
...file.tailwindUtilities,
|
|
1455
|
+
...file.cssClasses,
|
|
1456
|
+
...file.classExpressions.flatMap((expression) => expression.classes),
|
|
1457
|
+
]));
|
|
1458
|
+
const dependencyText = files.flatMap((file) => file.dependencies).join(' ').toLowerCase();
|
|
1459
|
+
const importText = files.flatMap((file) => file.imports).join(' ').toLowerCase();
|
|
1460
|
+
const pathText = files.map((file) => file.relPath).join(' ').toLowerCase();
|
|
1461
|
+
if (files.some((file) => file.cssRules.length || file.cssVariableValues.length || file.cssClasses.length)) {
|
|
1462
|
+
systems.add('raw-css');
|
|
1463
|
+
}
|
|
1464
|
+
if (files.some((file) => file.tailwindUtilities.length) ||
|
|
1465
|
+
/tailwind/.test(dependencyText) ||
|
|
1466
|
+
/tailwind/.test(importText) ||
|
|
1467
|
+
/tailwind\.config|tailwind-theme|@tailwind/.test(pathText)) {
|
|
1468
|
+
systems.add('tailwind');
|
|
1469
|
+
}
|
|
1470
|
+
if (allClasses.some(isBootstrapClass) ||
|
|
1471
|
+
/\bbootstrap\b/.test(dependencyText) ||
|
|
1472
|
+
/\bbootstrap\b/.test(importText)) {
|
|
1473
|
+
systems.add('bootstrap');
|
|
1474
|
+
}
|
|
1475
|
+
return Array.from(systems).length ? Array.from(systems) : ['unknown'];
|
|
1476
|
+
}
|
|
1477
|
+
function classNamesFromCssSelector(selector) {
|
|
1478
|
+
return (0, utils_1.unique)(Array.from(selector.matchAll(/\.((?:\\.|[A-Za-z0-9_-])+)/g))
|
|
1479
|
+
.map((match) => unescapeCssIdentifier(match[1]))
|
|
1480
|
+
.filter(Boolean)).slice(0, 24);
|
|
1481
|
+
}
|
|
1482
|
+
function unescapeCssIdentifier(value) {
|
|
1483
|
+
return value
|
|
1484
|
+
.replace(/\\:/g, ':')
|
|
1485
|
+
.replace(/\\\//g, '/')
|
|
1486
|
+
.replace(/\\\[/g, '[')
|
|
1487
|
+
.replace(/\\\]/g, ']')
|
|
1488
|
+
.replace(/\\\(/g, '(')
|
|
1489
|
+
.replace(/\\\)/g, ')')
|
|
1490
|
+
.replace(/\\,/g, ',')
|
|
1491
|
+
.replace(/\\\./g, '.')
|
|
1492
|
+
.replace(/\\/g, '');
|
|
1493
|
+
}
|
|
1494
|
+
function cssSelectorStatePrefixes(selector) {
|
|
1495
|
+
const prefixes = [];
|
|
1496
|
+
if (/:hover\b/.test(selector)) {
|
|
1497
|
+
prefixes.push('hover');
|
|
1498
|
+
}
|
|
1499
|
+
if (/:focus-visible\b/.test(selector)) {
|
|
1500
|
+
prefixes.push('focus-visible');
|
|
1501
|
+
}
|
|
1502
|
+
else if (/:focus\b/.test(selector)) {
|
|
1503
|
+
prefixes.push('focus');
|
|
1504
|
+
}
|
|
1505
|
+
if (/:active\b/.test(selector)) {
|
|
1506
|
+
prefixes.push('active');
|
|
1507
|
+
}
|
|
1508
|
+
if (/:disabled\b|\[disabled\]/.test(selector)) {
|
|
1509
|
+
prefixes.push('disabled');
|
|
1510
|
+
}
|
|
1511
|
+
if (/\[data-[^\]]+\]/.test(selector)) {
|
|
1512
|
+
prefixes.push('data');
|
|
1513
|
+
}
|
|
1514
|
+
return prefixes;
|
|
1515
|
+
}
|
|
1516
|
+
function cssRuleMediaContext(selector) {
|
|
1517
|
+
const mediaMatch = selector.match(/@media\s*([^{]+)\s+(.+)$/);
|
|
1518
|
+
return mediaMatch?.[1]?.trim();
|
|
1519
|
+
}
|
|
1520
|
+
function normalizeCssDeclarationValue(value) {
|
|
1521
|
+
return value.replace(/\s*!important\s*$/i, '').trim();
|
|
1522
|
+
}
|
|
1523
|
+
function cssDeclarationImportant(value) {
|
|
1524
|
+
return /\s!important\s*$/i.test(value);
|
|
1525
|
+
}
|
|
1526
|
+
function estimateSelectorSpecificity(selector) {
|
|
1527
|
+
const idCount = (selector.match(/#[A-Za-z0-9_-]+/g) ?? []).length;
|
|
1528
|
+
const classCount = (selector.match(/\.[A-Za-z0-9_\\-]+|\[[^\]]+\]|:[A-Za-z-]+/g) ?? []).length;
|
|
1529
|
+
const elementCount = (selector.match(/(^|[\s>+~])([A-Za-z][A-Za-z0-9-]*)/g) ?? []).length;
|
|
1530
|
+
return idCount * 100 + classCount * 10 + elementCount;
|
|
1531
|
+
}
|
|
1532
|
+
function resolveCssValueTokens(value, cssVariables) {
|
|
1533
|
+
return value.replace(/var\((--[A-Za-z0-9_-]+)(?:,[^)]+)?\)/g, (match, variableName) => {
|
|
1534
|
+
const variableValues = cssVariables?.get(variableName);
|
|
1535
|
+
const latest = variableValues?.[variableValues.length - 1];
|
|
1536
|
+
return latest ? `${match} /* ${latest.value} */` : match;
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
function resolveUiClassDeclarations(classes, styleContext) {
|
|
1540
|
+
const declarations = [];
|
|
1541
|
+
for (const className of classes) {
|
|
1542
|
+
const candidates = (0, utils_1.unique)([className, (0, utils_1.classBase)(className), (0, utils_1.classBase)(className).replace(/^!/, '')]);
|
|
1543
|
+
for (const candidate of candidates) {
|
|
1544
|
+
declarations.push(...(styleContext?.classRules.get(candidate) ?? []).map((declaration) => ({
|
|
1545
|
+
...declaration,
|
|
1546
|
+
className,
|
|
1547
|
+
})));
|
|
1548
|
+
}
|
|
1549
|
+
declarations.push(...tailwindDeclarationsForClass(className, styleContext));
|
|
1550
|
+
declarations.push(...bootstrapDeclarationsForClass(className, styleContext));
|
|
1551
|
+
}
|
|
1552
|
+
return (0, utils_1.uniqueBy)(declarations, (declaration) => [
|
|
1553
|
+
declaration.className,
|
|
1554
|
+
declaration.property,
|
|
1555
|
+
declaration.value,
|
|
1556
|
+
declaration.source,
|
|
1557
|
+
declaration.selector ?? '',
|
|
1558
|
+
declaration.path ?? '',
|
|
1559
|
+
declaration.line ?? '',
|
|
1560
|
+
].join(':'))
|
|
1561
|
+
.sort((a, b) => (a.cascadeRank ?? 0) - (b.cascadeRank ?? 0))
|
|
1562
|
+
.slice(0, 360);
|
|
1563
|
+
}
|
|
1564
|
+
function tailwindDeclarationsForClass(className, styleContext) {
|
|
1565
|
+
const base = (0, utils_1.classBase)(className).replace(/^!/, '');
|
|
1566
|
+
const prefixes = className.split(':').slice(0, -1);
|
|
1567
|
+
const declaration = (property, value) => ({
|
|
1568
|
+
property,
|
|
1569
|
+
value,
|
|
1570
|
+
source: 'tailwind-utility',
|
|
1571
|
+
styleSystem: 'tailwind',
|
|
1572
|
+
className,
|
|
1573
|
+
prefixes,
|
|
1574
|
+
cascadeRank: 10,
|
|
1575
|
+
});
|
|
1576
|
+
const declarations = [];
|
|
1577
|
+
if (base === 'flex')
|
|
1578
|
+
declarations.push(declaration('display', 'flex'));
|
|
1579
|
+
if (base === 'inline-flex')
|
|
1580
|
+
declarations.push(declaration('display', 'inline-flex'));
|
|
1581
|
+
if (base === 'grid')
|
|
1582
|
+
declarations.push(declaration('display', 'grid'));
|
|
1583
|
+
if (base === 'inline-grid')
|
|
1584
|
+
declarations.push(declaration('display', 'inline-grid'));
|
|
1585
|
+
if (base === 'block')
|
|
1586
|
+
declarations.push(declaration('display', 'block'));
|
|
1587
|
+
if (base === 'inline')
|
|
1588
|
+
declarations.push(declaration('display', 'inline'));
|
|
1589
|
+
if (base === 'inline-block')
|
|
1590
|
+
declarations.push(declaration('display', 'inline-block'));
|
|
1591
|
+
if (base === 'hidden')
|
|
1592
|
+
declarations.push(declaration('display', 'none'));
|
|
1593
|
+
if (base === 'contents')
|
|
1594
|
+
declarations.push(declaration('display', 'contents'));
|
|
1595
|
+
if (base === 'table')
|
|
1596
|
+
declarations.push(declaration('display', 'table'));
|
|
1597
|
+
if (base === 'flow-root')
|
|
1598
|
+
declarations.push(declaration('display', 'flow-root'));
|
|
1599
|
+
const alignment = tailwindAlignmentDeclaration(base);
|
|
1600
|
+
if (alignment)
|
|
1601
|
+
declarations.push(declaration(alignment.property, alignment.value));
|
|
1602
|
+
const spacing = tailwindSpacingDeclaration(base);
|
|
1603
|
+
declarations.push(...spacing.map((item) => declaration(item.property, item.value)));
|
|
1604
|
+
const sizing = tailwindSizingDeclaration(base);
|
|
1605
|
+
declarations.push(...sizing.map((item) => declaration(item.property, item.value)));
|
|
1606
|
+
const typography = tailwindTypographyDeclaration(base);
|
|
1607
|
+
declarations.push(...typography.map((item) => declaration(item.property, item.value)));
|
|
1608
|
+
const border = tailwindBorderDeclaration(base, styleContext);
|
|
1609
|
+
declarations.push(...border.map((item) => declaration(item.property, item.value)));
|
|
1610
|
+
const color = tailwindColorDeclaration(base, styleContext);
|
|
1611
|
+
declarations.push(...color.map((item) => declaration(item.property, item.value)));
|
|
1612
|
+
if (base === 'overflow-hidden')
|
|
1613
|
+
declarations.push(declaration('overflow', 'hidden'));
|
|
1614
|
+
if (base === 'overflow-visible')
|
|
1615
|
+
declarations.push(declaration('overflow', 'visible'));
|
|
1616
|
+
if (base === 'overflow-auto')
|
|
1617
|
+
declarations.push(declaration('overflow', 'auto'));
|
|
1618
|
+
if (base === 'overflow-scroll')
|
|
1619
|
+
declarations.push(declaration('overflow', 'scroll'));
|
|
1620
|
+
if (base === 'whitespace-nowrap')
|
|
1621
|
+
declarations.push(declaration('white-space', 'nowrap'));
|
|
1622
|
+
if (base === 'whitespace-normal')
|
|
1623
|
+
declarations.push(declaration('white-space', 'normal'));
|
|
1624
|
+
if (base === 'break-words')
|
|
1625
|
+
declarations.push(declaration('overflow-wrap', 'break-word'));
|
|
1626
|
+
if (base === 'truncate') {
|
|
1627
|
+
declarations.push(declaration('overflow', 'hidden'), declaration('text-overflow', 'ellipsis'), declaration('white-space', 'nowrap'));
|
|
1628
|
+
}
|
|
1629
|
+
if (/^line-clamp-(\d+)$/.test(base)) {
|
|
1630
|
+
const [, lines] = base.match(/^line-clamp-(\d+)$/) ?? [];
|
|
1631
|
+
declarations.push(declaration('overflow', 'hidden'), declaration('display', '-webkit-box'), declaration('-webkit-line-clamp', lines), declaration('-webkit-box-orient', 'vertical'));
|
|
1632
|
+
}
|
|
1633
|
+
if (/^shadow/.test(base)) {
|
|
1634
|
+
declarations.push(declaration('box-shadow', tailwindShadowValue(base)));
|
|
1635
|
+
}
|
|
1636
|
+
if (/^opacity-(\d+)$/.test(base)) {
|
|
1637
|
+
const [, value] = base.match(/^opacity-(\d+)$/) ?? [];
|
|
1638
|
+
declarations.push(declaration('opacity', String(Number(value) / 100)));
|
|
1639
|
+
}
|
|
1640
|
+
if (/^grid-cols-(\d+)$/.test(base)) {
|
|
1641
|
+
const [, value] = base.match(/^grid-cols-(\d+)$/) ?? [];
|
|
1642
|
+
declarations.push(declaration('grid-template-columns', `repeat(${value}, minmax(0, 1fr))`));
|
|
1643
|
+
}
|
|
1644
|
+
if (/^grid-cols-\[([^\]]+)\]$/.test(base)) {
|
|
1645
|
+
declarations.push(declaration('grid-template-columns', arbitraryValue(base).replace(/_/g, ' ')));
|
|
1646
|
+
}
|
|
1647
|
+
if (/^grid-rows-(\d+)$/.test(base)) {
|
|
1648
|
+
const [, value] = base.match(/^grid-rows-(\d+)$/) ?? [];
|
|
1649
|
+
declarations.push(declaration('grid-template-rows', `repeat(${value}, minmax(0, 1fr))`));
|
|
1650
|
+
}
|
|
1651
|
+
if (/^grid-rows-\[([^\]]+)\]$/.test(base)) {
|
|
1652
|
+
declarations.push(declaration('grid-template-rows', arbitraryValue(base).replace(/_/g, ' ')));
|
|
1653
|
+
}
|
|
1654
|
+
if (base === 'auto-rows-fr') {
|
|
1655
|
+
declarations.push(declaration('grid-auto-rows', 'minmax(0, 1fr)'));
|
|
1656
|
+
}
|
|
1657
|
+
if (/^auto-rows-\[([^\]]+)\]$/.test(base)) {
|
|
1658
|
+
declarations.push(declaration('grid-auto-rows', arbitraryValue(base).replace(/_/g, ' ')));
|
|
1659
|
+
}
|
|
1660
|
+
if (base === 'flex-row')
|
|
1661
|
+
declarations.push(declaration('flex-direction', 'row'));
|
|
1662
|
+
if (base === 'flex-col')
|
|
1663
|
+
declarations.push(declaration('flex-direction', 'column'));
|
|
1664
|
+
if (base === 'flex-wrap')
|
|
1665
|
+
declarations.push(declaration('flex-wrap', 'wrap'));
|
|
1666
|
+
if (base === 'flex-nowrap')
|
|
1667
|
+
declarations.push(declaration('flex-wrap', 'nowrap'));
|
|
1668
|
+
if (base === 'object-cover')
|
|
1669
|
+
declarations.push(declaration('object-fit', 'cover'));
|
|
1670
|
+
if (base === 'object-contain')
|
|
1671
|
+
declarations.push(declaration('object-fit', 'contain'));
|
|
1672
|
+
if (base === 'object-fill')
|
|
1673
|
+
declarations.push(declaration('object-fit', 'fill'));
|
|
1674
|
+
if (base === 'object-center')
|
|
1675
|
+
declarations.push(declaration('object-position', 'center'));
|
|
1676
|
+
if (/^aspect-\[([^\]]+)\]$/.test(base)) {
|
|
1677
|
+
declarations.push(declaration('aspect-ratio', arbitraryValue(base).replace(/_/g, ' ')));
|
|
1678
|
+
}
|
|
1679
|
+
if (/^col-span-(\d+)$/.test(base)) {
|
|
1680
|
+
const [, value] = base.match(/^col-span-(\d+)$/) ?? [];
|
|
1681
|
+
declarations.push(declaration('grid-column', `span ${value} / span ${value}`));
|
|
1682
|
+
}
|
|
1683
|
+
if (/^row-span-(\d+)$/.test(base)) {
|
|
1684
|
+
const [, value] = base.match(/^row-span-(\d+)$/) ?? [];
|
|
1685
|
+
declarations.push(declaration('grid-row', `span ${value} / span ${value}`));
|
|
1686
|
+
}
|
|
1687
|
+
if (/^(?:relative|absolute|fixed|sticky|static)$/.test(base)) {
|
|
1688
|
+
declarations.push(declaration('position', base));
|
|
1689
|
+
}
|
|
1690
|
+
if (/^(?:inset|top|right|bottom|left)-(.+)$/.test(base)) {
|
|
1691
|
+
const [, property, token] = base.match(/^(inset|top|right|bottom|left)-(.+)$/) ?? [];
|
|
1692
|
+
const value = tailwindScaleValue(token);
|
|
1693
|
+
if (value)
|
|
1694
|
+
declarations.push(declaration(property, value));
|
|
1695
|
+
}
|
|
1696
|
+
if (/^z-\[([^\]]+)\]$/.test(base)) {
|
|
1697
|
+
declarations.push(declaration('z-index', arbitraryValue(base)));
|
|
1698
|
+
}
|
|
1699
|
+
if (/^z-(\d+)$/.test(base)) {
|
|
1700
|
+
const [, value] = base.match(/^z-(\d+)$/) ?? [];
|
|
1701
|
+
declarations.push(declaration('z-index', value));
|
|
1702
|
+
}
|
|
1703
|
+
return declarations;
|
|
1704
|
+
}
|
|
1705
|
+
function tailwindAlignmentDeclaration(base) {
|
|
1706
|
+
const alignItems = {
|
|
1707
|
+
'items-start': 'flex-start',
|
|
1708
|
+
'items-end': 'flex-end',
|
|
1709
|
+
'items-center': 'center',
|
|
1710
|
+
'items-baseline': 'baseline',
|
|
1711
|
+
'items-stretch': 'stretch',
|
|
1712
|
+
};
|
|
1713
|
+
const justifyContent = {
|
|
1714
|
+
'justify-start': 'flex-start',
|
|
1715
|
+
'justify-end': 'flex-end',
|
|
1716
|
+
'justify-center': 'center',
|
|
1717
|
+
'justify-between': 'space-between',
|
|
1718
|
+
'justify-around': 'space-around',
|
|
1719
|
+
'justify-evenly': 'space-evenly',
|
|
1720
|
+
};
|
|
1721
|
+
if (alignItems[base])
|
|
1722
|
+
return { property: 'align-items', value: alignItems[base] };
|
|
1723
|
+
if (justifyContent[base])
|
|
1724
|
+
return { property: 'justify-content', value: justifyContent[base] };
|
|
1725
|
+
return undefined;
|
|
1726
|
+
}
|
|
1727
|
+
function tailwindSpacingDeclaration(base) {
|
|
1728
|
+
const spacingMatch = base.match(/^(p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y)-(.+)$/);
|
|
1729
|
+
if (!spacingMatch) {
|
|
1730
|
+
return [];
|
|
1731
|
+
}
|
|
1732
|
+
const [, prefix, token] = spacingMatch;
|
|
1733
|
+
const value = token === 'auto' ? 'auto' : tailwindScaleValue(token);
|
|
1734
|
+
if (!value) {
|
|
1735
|
+
return [];
|
|
1736
|
+
}
|
|
1737
|
+
const properties = {
|
|
1738
|
+
p: ['padding'],
|
|
1739
|
+
px: ['padding-left', 'padding-right'],
|
|
1740
|
+
py: ['padding-top', 'padding-bottom'],
|
|
1741
|
+
pt: ['padding-top'],
|
|
1742
|
+
pr: ['padding-right'],
|
|
1743
|
+
pb: ['padding-bottom'],
|
|
1744
|
+
pl: ['padding-left'],
|
|
1745
|
+
m: ['margin'],
|
|
1746
|
+
mx: ['margin-left', 'margin-right'],
|
|
1747
|
+
my: ['margin-top', 'margin-bottom'],
|
|
1748
|
+
mt: ['margin-top'],
|
|
1749
|
+
mr: ['margin-right'],
|
|
1750
|
+
mb: ['margin-bottom'],
|
|
1751
|
+
ml: ['margin-left'],
|
|
1752
|
+
gap: ['gap'],
|
|
1753
|
+
'gap-x': ['column-gap'],
|
|
1754
|
+
'gap-y': ['row-gap'],
|
|
1755
|
+
};
|
|
1756
|
+
return (properties[prefix] ?? []).map((property) => ({ property, value }));
|
|
1757
|
+
}
|
|
1758
|
+
function tailwindSizingDeclaration(base) {
|
|
1759
|
+
if (base === 'min-w-0')
|
|
1760
|
+
return [{ property: 'min-width', value: '0px' }];
|
|
1761
|
+
if (base === 'min-h-0')
|
|
1762
|
+
return [{ property: 'min-height', value: '0px' }];
|
|
1763
|
+
if (base === 'w-px')
|
|
1764
|
+
return [{ property: 'width', value: '1px' }];
|
|
1765
|
+
if (base === 'h-px')
|
|
1766
|
+
return [{ property: 'height', value: '1px' }];
|
|
1767
|
+
if (base === 'w-full')
|
|
1768
|
+
return [{ property: 'width', value: '100%' }];
|
|
1769
|
+
if (base === 'h-full')
|
|
1770
|
+
return [{ property: 'height', value: '100%' }];
|
|
1771
|
+
if (base === 'max-w-none')
|
|
1772
|
+
return [{ property: 'max-width', value: 'none' }];
|
|
1773
|
+
if (base === 'shrink-0')
|
|
1774
|
+
return [{ property: 'flex-shrink', value: '0' }];
|
|
1775
|
+
if (base === 'shrink')
|
|
1776
|
+
return [{ property: 'flex-shrink', value: '1' }];
|
|
1777
|
+
if (base === 'grow')
|
|
1778
|
+
return [{ property: 'flex-grow', value: '1' }];
|
|
1779
|
+
if (base === 'grow-0')
|
|
1780
|
+
return [{ property: 'flex-grow', value: '0' }];
|
|
1781
|
+
const sizeMatch = base.match(/^(size|w|h|min-w|min-h|max-w|max-h)-(.+)$/);
|
|
1782
|
+
if (!sizeMatch) {
|
|
1783
|
+
return [];
|
|
1784
|
+
}
|
|
1785
|
+
const [, prefix, token] = sizeMatch;
|
|
1786
|
+
const value = tailwindScaleValue(token) ?? tailwindNamedSizeValue(token);
|
|
1787
|
+
if (!value) {
|
|
1788
|
+
return [];
|
|
1789
|
+
}
|
|
1790
|
+
const properties = {
|
|
1791
|
+
size: ['width', 'height'],
|
|
1792
|
+
w: ['width'],
|
|
1793
|
+
h: ['height'],
|
|
1794
|
+
'min-w': ['min-width'],
|
|
1795
|
+
'min-h': ['min-height'],
|
|
1796
|
+
'max-w': ['max-width'],
|
|
1797
|
+
'max-h': ['max-height'],
|
|
1798
|
+
};
|
|
1799
|
+
return (properties[prefix] ?? []).map((property) => ({ property, value }));
|
|
1800
|
+
}
|
|
1801
|
+
function tailwindTypographyDeclaration(base) {
|
|
1802
|
+
const declarations = [];
|
|
1803
|
+
const textMatch = base.match(/^text-(.+)$/);
|
|
1804
|
+
if (textMatch) {
|
|
1805
|
+
const token = textMatch[1];
|
|
1806
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1807
|
+
if (arbitrary && looksLikeCssLength(arbitrary)) {
|
|
1808
|
+
declarations.push({ property: 'font-size', value: arbitrary });
|
|
1809
|
+
}
|
|
1810
|
+
else if (tailwindFontSizeScale[token]) {
|
|
1811
|
+
declarations.push({ property: 'font-size', value: tailwindFontSizeScale[token] });
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
const weightMatch = base.match(/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/);
|
|
1815
|
+
if (weightMatch)
|
|
1816
|
+
declarations.push({ property: 'font-weight', value: tailwindFontWeightScale[weightMatch[1]] });
|
|
1817
|
+
const leadingMatch = base.match(/^leading-(.+)$/);
|
|
1818
|
+
if (leadingMatch) {
|
|
1819
|
+
const token = leadingMatch[1];
|
|
1820
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1821
|
+
const value = arbitrary ?? tailwindLineHeightScale[token] ?? tailwindScaleValue(token);
|
|
1822
|
+
if (value)
|
|
1823
|
+
declarations.push({ property: 'line-height', value });
|
|
1824
|
+
}
|
|
1825
|
+
const trackingMatch = base.match(/^tracking-(.+)$/);
|
|
1826
|
+
if (trackingMatch) {
|
|
1827
|
+
const token = trackingMatch[1];
|
|
1828
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1829
|
+
const value = arbitrary ?? tailwindTrackingScale[token];
|
|
1830
|
+
if (value)
|
|
1831
|
+
declarations.push({ property: 'letter-spacing', value });
|
|
1832
|
+
}
|
|
1833
|
+
if (base === 'uppercase')
|
|
1834
|
+
declarations.push({ property: 'text-transform', value: 'uppercase' });
|
|
1835
|
+
if (base === 'lowercase')
|
|
1836
|
+
declarations.push({ property: 'text-transform', value: 'lowercase' });
|
|
1837
|
+
if (base === 'capitalize')
|
|
1838
|
+
declarations.push({ property: 'text-transform', value: 'capitalize' });
|
|
1839
|
+
if (base === 'normal-case')
|
|
1840
|
+
declarations.push({ property: 'text-transform', value: 'none' });
|
|
1841
|
+
if (base === 'text-left')
|
|
1842
|
+
declarations.push({ property: 'text-align', value: 'left' });
|
|
1843
|
+
if (base === 'text-center')
|
|
1844
|
+
declarations.push({ property: 'text-align', value: 'center' });
|
|
1845
|
+
if (base === 'text-right')
|
|
1846
|
+
declarations.push({ property: 'text-align', value: 'right' });
|
|
1847
|
+
return declarations;
|
|
1848
|
+
}
|
|
1849
|
+
function tailwindBorderDeclaration(base, styleContext) {
|
|
1850
|
+
if (base === 'border')
|
|
1851
|
+
return [{ property: 'border-width', value: '1px' }];
|
|
1852
|
+
if (/^border-(\d+)$/.test(base)) {
|
|
1853
|
+
const [, value] = base.match(/^border-(\d+)$/) ?? [];
|
|
1854
|
+
return [{ property: 'border-width', value: `${value}px` }];
|
|
1855
|
+
}
|
|
1856
|
+
const roundedMatch = base.match(/^rounded(?:-(.+))?$/);
|
|
1857
|
+
if (roundedMatch) {
|
|
1858
|
+
const token = roundedMatch[1] ?? 'DEFAULT';
|
|
1859
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1860
|
+
const value = arbitrary ?? tailwindRadiusScale[token] ?? cssTokenValue('radius', styleContext);
|
|
1861
|
+
return value ? [{ property: 'border-radius', value }] : [];
|
|
1862
|
+
}
|
|
1863
|
+
if (base.startsWith('border-') && !/^border-(?:t|r|b|l|x|y)$/.test(base)) {
|
|
1864
|
+
return [{ property: 'border-color', value: tailwindColorValue(base.slice('border-'.length), styleContext) }];
|
|
1865
|
+
}
|
|
1866
|
+
return [];
|
|
1867
|
+
}
|
|
1868
|
+
function tailwindColorDeclaration(base, styleContext) {
|
|
1869
|
+
const bgMatch = base.match(/^bg-(.+)$/);
|
|
1870
|
+
if (bgMatch) {
|
|
1871
|
+
return [{ property: 'background-color', value: tailwindColorValue(bgMatch[1], styleContext) }];
|
|
1872
|
+
}
|
|
1873
|
+
const textMatch = base.match(/^text-(.+)$/);
|
|
1874
|
+
if (textMatch && !tailwindFontSizeScale[textMatch[1]] && !looksLikeCssLength(arbitraryTokenValue(textMatch[1]) ?? '')) {
|
|
1875
|
+
return [{ property: 'color', value: tailwindColorValue(textMatch[1], styleContext) }];
|
|
1876
|
+
}
|
|
1877
|
+
const fillMatch = base.match(/^fill-(.+)$/);
|
|
1878
|
+
if (fillMatch)
|
|
1879
|
+
return [{ property: 'fill', value: tailwindColorValue(fillMatch[1], styleContext) }];
|
|
1880
|
+
const strokeMatch = base.match(/^stroke-(.+)$/);
|
|
1881
|
+
if (strokeMatch)
|
|
1882
|
+
return [{ property: 'stroke', value: tailwindColorValue(strokeMatch[1], styleContext) }];
|
|
1883
|
+
return [];
|
|
1884
|
+
}
|
|
1885
|
+
function tailwindColorValue(token, styleContext) {
|
|
1886
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1887
|
+
if (arbitrary) {
|
|
1888
|
+
return resolveCssValueTokens(arbitrary.replace(/_/g, ' '), styleContext?.cssVariables);
|
|
1889
|
+
}
|
|
1890
|
+
const [name, opacity] = token.split('/');
|
|
1891
|
+
const cssVariable = cssTokenValue(name, styleContext);
|
|
1892
|
+
const baseValue = cssVariable ?? colorKeywordValue(name) ?? `token(${name})`;
|
|
1893
|
+
return opacity ? `${baseValue} / ${opacityToPercent(opacity)}` : baseValue;
|
|
1894
|
+
}
|
|
1895
|
+
function cssTokenValue(token, styleContext) {
|
|
1896
|
+
const candidates = (0, utils_1.unique)([`--${token}`, `--color-${token}`, `--${token.replace(/\//g, '-')}`]);
|
|
1897
|
+
const match = candidates.find((candidate) => styleContext?.cssVariables.has(candidate));
|
|
1898
|
+
return match ? `var(${match})` : undefined;
|
|
1899
|
+
}
|
|
1900
|
+
function colorKeywordValue(token) {
|
|
1901
|
+
const colors = {
|
|
1902
|
+
transparent: 'transparent',
|
|
1903
|
+
current: 'currentColor',
|
|
1904
|
+
black: '#000000',
|
|
1905
|
+
white: '#ffffff',
|
|
1906
|
+
};
|
|
1907
|
+
return colors[token];
|
|
1908
|
+
}
|
|
1909
|
+
function opacityToPercent(value) {
|
|
1910
|
+
const arbitrary = arbitraryTokenValue(value);
|
|
1911
|
+
if (arbitrary) {
|
|
1912
|
+
const numericArbitrary = Number(arbitrary);
|
|
1913
|
+
if (Number.isFinite(numericArbitrary)) {
|
|
1914
|
+
return `${numericArbitrary * 100}%`;
|
|
1915
|
+
}
|
|
1916
|
+
return arbitrary;
|
|
1917
|
+
}
|
|
1918
|
+
const numeric = Number(value);
|
|
1919
|
+
if (Number.isFinite(numeric)) {
|
|
1920
|
+
return `${numeric}%`;
|
|
1921
|
+
}
|
|
1922
|
+
return value;
|
|
1923
|
+
}
|
|
1924
|
+
function tailwindScaleValue(token) {
|
|
1925
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1926
|
+
if (arbitrary) {
|
|
1927
|
+
return arbitrary.replace(/_/g, ' ');
|
|
1928
|
+
}
|
|
1929
|
+
const negative = token.startsWith('-');
|
|
1930
|
+
const lookup = negative ? token.slice(1) : token;
|
|
1931
|
+
const value = tailwindSpacingScale[lookup];
|
|
1932
|
+
return value ? `${negative ? '-' : ''}${value}` : undefined;
|
|
1933
|
+
}
|
|
1934
|
+
function tailwindNamedSizeValue(token) {
|
|
1935
|
+
const values = {
|
|
1936
|
+
auto: 'auto',
|
|
1937
|
+
full: '100%',
|
|
1938
|
+
screen: '100vw',
|
|
1939
|
+
svw: '100svw',
|
|
1940
|
+
lvw: '100lvw',
|
|
1941
|
+
dvw: '100dvw',
|
|
1942
|
+
min: 'min-content',
|
|
1943
|
+
max: 'max-content',
|
|
1944
|
+
fit: 'fit-content',
|
|
1945
|
+
prose: '65ch',
|
|
1946
|
+
xs: '20rem',
|
|
1947
|
+
sm: '24rem',
|
|
1948
|
+
md: '28rem',
|
|
1949
|
+
lg: '32rem',
|
|
1950
|
+
xl: '36rem',
|
|
1951
|
+
'2xl': '42rem',
|
|
1952
|
+
'3xl': '48rem',
|
|
1953
|
+
'4xl': '56rem',
|
|
1954
|
+
'5xl': '64rem',
|
|
1955
|
+
'6xl': '72rem',
|
|
1956
|
+
'7xl': '80rem',
|
|
1957
|
+
};
|
|
1958
|
+
return values[token];
|
|
1959
|
+
}
|
|
1960
|
+
function arbitraryTokenValue(token) {
|
|
1961
|
+
const match = token.match(/^\[(.+)\]$/);
|
|
1962
|
+
return match?.[1];
|
|
1963
|
+
}
|
|
1964
|
+
function arbitraryValue(base) {
|
|
1965
|
+
return base.match(/\[(.+)\]/)?.[1] ?? '';
|
|
1966
|
+
}
|
|
1967
|
+
function looksLikeCssLength(value) {
|
|
1968
|
+
return /^-?(?:\d|\.)/.test(value) || /^var\(--/.test(value) || /^clamp\(/.test(value);
|
|
1969
|
+
}
|
|
1970
|
+
function tailwindShadowValue(base) {
|
|
1971
|
+
const arbitrary = arbitraryValue(base);
|
|
1972
|
+
if (arbitrary) {
|
|
1973
|
+
return arbitrary.replace(/_/g, ' ');
|
|
1974
|
+
}
|
|
1975
|
+
if (base === 'shadow-none') {
|
|
1976
|
+
return 'none';
|
|
1977
|
+
}
|
|
1978
|
+
return `tailwind(${base})`;
|
|
1979
|
+
}
|
|
1980
|
+
function bootstrapDeclarationsForClass(className, styleContext) {
|
|
1981
|
+
const base = (0, utils_1.classBase)(className).replace(/^!/, '');
|
|
1982
|
+
if (!isBootstrapClass(base)) {
|
|
1983
|
+
return [];
|
|
1984
|
+
}
|
|
1985
|
+
const prefixes = bootstrapResponsivePrefixes(base);
|
|
1986
|
+
const normalized = stripBootstrapResponsiveInfix(base);
|
|
1987
|
+
const declaration = (property, value) => ({
|
|
1988
|
+
property,
|
|
1989
|
+
value: resolveCssValueTokens(value, styleContext?.cssVariables),
|
|
1990
|
+
source: 'bootstrap-utility',
|
|
1991
|
+
styleSystem: 'bootstrap',
|
|
1992
|
+
className,
|
|
1993
|
+
prefixes,
|
|
1994
|
+
cascadeRank: 20,
|
|
1995
|
+
});
|
|
1996
|
+
const declarations = [];
|
|
1997
|
+
declarations.push(...bootstrapComponentDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1998
|
+
declarations.push(...bootstrapSpacingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1999
|
+
declarations.push(...bootstrapDisplayDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
2000
|
+
declarations.push(...bootstrapFlexDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
2001
|
+
declarations.push(...bootstrapSizingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
2002
|
+
declarations.push(...bootstrapTypographyDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
2003
|
+
declarations.push(...bootstrapColorDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
2004
|
+
declarations.push(...bootstrapBorderDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
2005
|
+
declarations.push(...bootstrapGridDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
2006
|
+
return declarations;
|
|
2007
|
+
}
|
|
2008
|
+
function isBootstrapClass(className) {
|
|
2009
|
+
const base = stripBootstrapResponsiveInfix((0, utils_1.classBase)(className));
|
|
2010
|
+
return /^(?:container|container-fluid|row|col|col-\d+|card|card-(?:body|title|text|header|footer)|btn|btn-(?:primary|secondary|success|danger|warning|info|light|dark|link|outline-primary|outline-secondary|outline-success|outline-danger|outline-warning|outline-info|outline-light|outline-dark)|badge|rounded|rounded-\d|rounded-pill|rounded-circle|border|border-\d|border-(?:0|top|end|bottom|start|primary|secondary|success|danger|warning|info|light|dark|white)|bg-|text-|fw-|fs-|lh-|display-|lead|small|d-|flex-|justify-content-|align-items-|align-self-|order-|m[stebxy]?-|p[stebxy]?-|gap-|w-|h-|mw-|mh-|min-vh-|min-vw-|vw-|vh-|form-|input-group|navbar|nav|nav-|list-group|modal|alert|shadow|overflow-|position-|top-|bottom-|start-|end-|translate-middle|visually-hidden)/.test(base);
|
|
2011
|
+
}
|
|
2012
|
+
function bootstrapResponsivePrefixes(className) {
|
|
2013
|
+
const match = className.match(/^(?:d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-((?:sm|md|lg|xl|xxl))-/);
|
|
2014
|
+
return match ? [match[1]] : [];
|
|
2015
|
+
}
|
|
2016
|
+
function stripBootstrapResponsiveInfix(className) {
|
|
2017
|
+
return className.replace(/^(d|col|offset|order|m[stebxy]?|p[stebxy]?|gap|text|float|flex|justify-content|align-items|align-self)-(sm|md|lg|xl|xxl)-/, '$1-');
|
|
2018
|
+
}
|
|
2019
|
+
function bootstrapComponentDeclarations(base) {
|
|
2020
|
+
const declarations = [];
|
|
2021
|
+
if (base === 'container' || base === 'container-fluid') {
|
|
2022
|
+
declarations.push({ property: 'width', value: '100%' }, { property: 'padding-right', value: 'calc(var(--bs-gutter-x, 1.5rem) * 0.5)' }, { property: 'padding-left', value: 'calc(var(--bs-gutter-x, 1.5rem) * 0.5)' }, { property: 'margin-right', value: 'auto' }, { property: 'margin-left', value: 'auto' });
|
|
2023
|
+
}
|
|
2024
|
+
if (base === 'row') {
|
|
2025
|
+
declarations.push({ property: 'display', value: 'flex' }, { property: 'flex-wrap', value: 'wrap' }, { property: 'margin-top', value: 'calc(-1 * var(--bs-gutter-y, 0))' }, { property: 'margin-right', value: 'calc(-0.5 * var(--bs-gutter-x, 1.5rem))' }, { property: 'margin-left', value: 'calc(-0.5 * var(--bs-gutter-x, 1.5rem))' });
|
|
2026
|
+
}
|
|
2027
|
+
if (base === 'card') {
|
|
2028
|
+
declarations.push({ property: 'position', value: 'relative' }, { property: 'display', value: 'flex' }, { property: 'flex-direction', value: 'column' }, { property: 'min-width', value: '0' }, { property: 'color', value: 'var(--bs-body-color)' }, { property: 'background-color', value: 'var(--bs-card-bg)' }, { property: 'border', value: 'var(--bs-card-border-width) solid var(--bs-card-border-color)' }, { property: 'border-radius', value: 'var(--bs-card-border-radius)' });
|
|
2029
|
+
}
|
|
2030
|
+
if (base === 'card-body') {
|
|
2031
|
+
declarations.push({ property: 'flex', value: '1 1 auto' }, { property: 'padding', value: 'var(--bs-card-spacer-y) var(--bs-card-spacer-x)' }, { property: 'color', value: 'var(--bs-card-color)' });
|
|
2032
|
+
}
|
|
2033
|
+
if (base === 'btn') {
|
|
2034
|
+
declarations.push({ property: 'display', value: 'inline-block' }, { property: 'padding', value: 'var(--bs-btn-padding-y) var(--bs-btn-padding-x)' }, { property: 'font-size', value: 'var(--bs-btn-font-size)' }, { property: 'font-weight', value: 'var(--bs-btn-font-weight)' }, { property: 'line-height', value: 'var(--bs-btn-line-height)' }, { property: 'text-align', value: 'center' }, { property: 'text-decoration', value: 'none' }, { property: 'vertical-align', value: 'middle' }, { property: 'cursor', value: 'pointer' }, { property: 'border', value: 'var(--bs-btn-border-width) solid var(--bs-btn-border-color)' }, { property: 'border-radius', value: 'var(--bs-btn-border-radius)' });
|
|
2035
|
+
}
|
|
2036
|
+
if (base.startsWith('btn-') && base !== 'btn-link') {
|
|
2037
|
+
const tone = base.replace(/^btn-outline-/, '').replace(/^btn-/, '');
|
|
2038
|
+
const outline = base.startsWith('btn-outline-');
|
|
2039
|
+
declarations.push({ property: 'color', value: outline ? `var(--bs-${tone})` : `var(--bs-btn-color)` }, {
|
|
2040
|
+
property: 'background-color',
|
|
2041
|
+
value: outline ? 'transparent' : `var(--bs-${tone})`,
|
|
2042
|
+
}, { property: 'border-color', value: `var(--bs-${tone})` });
|
|
2043
|
+
}
|
|
2044
|
+
if (base === 'btn-link') {
|
|
2045
|
+
declarations.push({ property: 'font-weight', value: '400' }, { property: 'color', value: 'var(--bs-link-color)' }, { property: 'text-decoration', value: 'underline' }, { property: 'background-color', value: 'transparent' }, { property: 'border-color', value: 'transparent' });
|
|
2046
|
+
}
|
|
2047
|
+
if (base === 'badge') {
|
|
2048
|
+
declarations.push({ property: 'display', value: 'inline-block' }, { property: 'padding', value: '0.35em 0.65em' }, { property: 'font-size', value: '0.75em' }, { property: 'font-weight', value: '700' }, { property: 'line-height', value: '1' }, { property: 'color', value: '#fff' }, { property: 'text-align', value: 'center' }, { property: 'white-space', value: 'nowrap' }, { property: 'vertical-align', value: 'baseline' }, { property: 'border-radius', value: 'var(--bs-border-radius)' });
|
|
2049
|
+
}
|
|
2050
|
+
if (base === 'form-control' || base === 'form-select') {
|
|
2051
|
+
declarations.push({ property: 'display', value: 'block' }, { property: 'width', value: '100%' }, { property: 'padding', value: '0.375rem 0.75rem' }, { property: 'font-size', value: '1rem' }, { property: 'font-weight', value: '400' }, { property: 'line-height', value: '1.5' }, { property: 'color', value: 'var(--bs-body-color)' }, { property: 'background-color', value: 'var(--bs-body-bg)' }, { property: 'border', value: 'var(--bs-border-width) solid var(--bs-border-color)' }, { property: 'border-radius', value: 'var(--bs-border-radius)' });
|
|
2052
|
+
}
|
|
2053
|
+
if (base === 'form-label') {
|
|
2054
|
+
declarations.push({ property: 'margin-bottom', value: '0.5rem' }, { property: 'display', value: 'inline-block' });
|
|
2055
|
+
}
|
|
2056
|
+
if (base === 'navbar') {
|
|
2057
|
+
declarations.push({ property: 'position', value: 'relative' }, { property: 'display', value: 'flex' }, { property: 'flex-wrap', value: 'wrap' }, { property: 'align-items', value: 'center' }, { property: 'justify-content', value: 'space-between' }, { property: 'padding', value: 'var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)' });
|
|
2058
|
+
}
|
|
2059
|
+
if (base === 'nav') {
|
|
2060
|
+
declarations.push({ property: 'display', value: 'flex' }, { property: 'flex-wrap', value: 'wrap' }, { property: 'padding-left', value: '0' }, { property: 'margin-bottom', value: '0' }, { property: 'list-style', value: 'none' });
|
|
2061
|
+
}
|
|
2062
|
+
if (base === 'nav-link') {
|
|
2063
|
+
declarations.push({ property: 'display', value: 'block' }, { property: 'padding', value: 'var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x)' }, { property: 'font-size', value: 'var(--bs-nav-link-font-size)' }, { property: 'font-weight', value: 'var(--bs-nav-link-font-weight)' }, { property: 'color', value: 'var(--bs-nav-link-color)' }, { property: 'text-decoration', value: 'none' });
|
|
2064
|
+
}
|
|
2065
|
+
if (base === 'alert') {
|
|
2066
|
+
declarations.push({ property: 'position', value: 'relative' }, { property: 'padding', value: 'var(--bs-alert-padding-y) var(--bs-alert-padding-x)' }, { property: 'margin-bottom', value: '1rem' }, { property: 'border', value: 'var(--bs-alert-border) solid var(--bs-alert-border-color)' }, { property: 'border-radius', value: 'var(--bs-alert-border-radius)' });
|
|
2067
|
+
}
|
|
2068
|
+
return declarations;
|
|
2069
|
+
}
|
|
2070
|
+
function bootstrapSpacingDeclarations(base) {
|
|
2071
|
+
const match = base.match(/^(m|p)([stebxy]?)-([0-5]|auto)$/);
|
|
2072
|
+
if (!match) {
|
|
2073
|
+
return [];
|
|
2074
|
+
}
|
|
2075
|
+
const [, kind, direction, token] = match;
|
|
2076
|
+
const value = bootstrapSpacingScale[token];
|
|
2077
|
+
const propertyRoot = kind === 'm' ? 'margin' : 'padding';
|
|
2078
|
+
const directionMap = {
|
|
2079
|
+
'': [propertyRoot],
|
|
2080
|
+
t: [`${propertyRoot}-top`],
|
|
2081
|
+
e: [`${propertyRoot}-right`],
|
|
2082
|
+
b: [`${propertyRoot}-bottom`],
|
|
2083
|
+
s: [`${propertyRoot}-left`],
|
|
2084
|
+
x: [`${propertyRoot}-left`, `${propertyRoot}-right`],
|
|
2085
|
+
y: [`${propertyRoot}-top`, `${propertyRoot}-bottom`],
|
|
2086
|
+
};
|
|
2087
|
+
return (directionMap[direction] ?? []).map((property) => ({ property, value }));
|
|
2088
|
+
}
|
|
2089
|
+
function bootstrapDisplayDeclarations(base) {
|
|
2090
|
+
const match = base.match(/^d-(none|inline|inline-block|block|grid|inline-grid|table|table-row|table-cell|flex|inline-flex)$/);
|
|
2091
|
+
return match ? [{ property: 'display', value: match[1] }] : [];
|
|
2092
|
+
}
|
|
2093
|
+
function bootstrapFlexDeclarations(base) {
|
|
2094
|
+
const declarations = [];
|
|
2095
|
+
const justifyMap = {
|
|
2096
|
+
start: 'flex-start',
|
|
2097
|
+
end: 'flex-end',
|
|
2098
|
+
center: 'center',
|
|
2099
|
+
between: 'space-between',
|
|
2100
|
+
around: 'space-around',
|
|
2101
|
+
evenly: 'space-evenly',
|
|
2102
|
+
};
|
|
2103
|
+
const alignMap = {
|
|
2104
|
+
start: 'flex-start',
|
|
2105
|
+
end: 'flex-end',
|
|
2106
|
+
center: 'center',
|
|
2107
|
+
baseline: 'baseline',
|
|
2108
|
+
stretch: 'stretch',
|
|
2109
|
+
};
|
|
2110
|
+
if (base === 'flex-row')
|
|
2111
|
+
declarations.push({ property: 'flex-direction', value: 'row' });
|
|
2112
|
+
if (base === 'flex-column')
|
|
2113
|
+
declarations.push({ property: 'flex-direction', value: 'column' });
|
|
2114
|
+
if (base === 'flex-wrap')
|
|
2115
|
+
declarations.push({ property: 'flex-wrap', value: 'wrap' });
|
|
2116
|
+
if (base === 'flex-nowrap')
|
|
2117
|
+
declarations.push({ property: 'flex-wrap', value: 'nowrap' });
|
|
2118
|
+
const justify = base.match(/^justify-content-(start|end|center|between|around|evenly)$/);
|
|
2119
|
+
if (justify)
|
|
2120
|
+
declarations.push({ property: 'justify-content', value: justifyMap[justify[1]] });
|
|
2121
|
+
const alignItems = base.match(/^align-items-(start|end|center|baseline|stretch)$/);
|
|
2122
|
+
if (alignItems)
|
|
2123
|
+
declarations.push({ property: 'align-items', value: alignMap[alignItems[1]] });
|
|
2124
|
+
const alignSelf = base.match(/^align-self-(start|end|center|baseline|stretch)$/);
|
|
2125
|
+
if (alignSelf)
|
|
2126
|
+
declarations.push({ property: 'align-self', value: alignMap[alignSelf[1]] });
|
|
2127
|
+
return declarations;
|
|
2128
|
+
}
|
|
2129
|
+
function bootstrapSizingDeclarations(base) {
|
|
2130
|
+
const percentMatch = base.match(/^(w|h|mw|mh)-(\d+)$/);
|
|
2131
|
+
if (percentMatch) {
|
|
2132
|
+
const [, prefix, amount] = percentMatch;
|
|
2133
|
+
const propertyMap = {
|
|
2134
|
+
w: 'width',
|
|
2135
|
+
h: 'height',
|
|
2136
|
+
mw: 'max-width',
|
|
2137
|
+
mh: 'max-height',
|
|
2138
|
+
};
|
|
2139
|
+
return [{ property: propertyMap[prefix], value: `${amount}%` }];
|
|
2140
|
+
}
|
|
2141
|
+
if (base === 'min-vh-100')
|
|
2142
|
+
return [{ property: 'min-height', value: '100vh' }];
|
|
2143
|
+
if (base === 'min-vw-100')
|
|
2144
|
+
return [{ property: 'min-width', value: '100vw' }];
|
|
2145
|
+
if (base === 'vh-100')
|
|
2146
|
+
return [{ property: 'height', value: '100vh' }];
|
|
2147
|
+
if (base === 'vw-100')
|
|
2148
|
+
return [{ property: 'width', value: '100vw' }];
|
|
2149
|
+
return [];
|
|
2150
|
+
}
|
|
2151
|
+
function bootstrapTypographyDeclarations(base) {
|
|
2152
|
+
const declarations = [];
|
|
2153
|
+
const fsMatch = base.match(/^fs-([1-6])$/);
|
|
2154
|
+
if (fsMatch)
|
|
2155
|
+
declarations.push({ property: 'font-size', value: bootstrapFontSizeScale[fsMatch[1]] });
|
|
2156
|
+
const fwMap = {
|
|
2157
|
+
lighter: 'lighter',
|
|
2158
|
+
light: '300',
|
|
2159
|
+
normal: '400',
|
|
2160
|
+
semibold: '600',
|
|
2161
|
+
bold: '700',
|
|
2162
|
+
bolder: 'bolder',
|
|
2163
|
+
};
|
|
2164
|
+
const fwMatch = base.match(/^fw-(lighter|light|normal|semibold|bold|bolder)$/);
|
|
2165
|
+
if (fwMatch)
|
|
2166
|
+
declarations.push({ property: 'font-weight', value: fwMap[fwMatch[1]] });
|
|
2167
|
+
const textAlign = base.match(/^text-(start|end|center)$/);
|
|
2168
|
+
if (textAlign) {
|
|
2169
|
+
declarations.push({ property: 'text-align', value: textAlign[1] === 'start' ? 'left' : textAlign[1] === 'end' ? 'right' : 'center' });
|
|
2170
|
+
}
|
|
2171
|
+
if (base === 'text-uppercase')
|
|
2172
|
+
declarations.push({ property: 'text-transform', value: 'uppercase' });
|
|
2173
|
+
if (base === 'text-lowercase')
|
|
2174
|
+
declarations.push({ property: 'text-transform', value: 'lowercase' });
|
|
2175
|
+
if (base === 'text-capitalize')
|
|
2176
|
+
declarations.push({ property: 'text-transform', value: 'capitalize' });
|
|
2177
|
+
if (base === 'lead') {
|
|
2178
|
+
declarations.push({ property: 'font-size', value: '1.25rem' }, { property: 'font-weight', value: '300' });
|
|
2179
|
+
}
|
|
2180
|
+
if (base === 'small')
|
|
2181
|
+
declarations.push({ property: 'font-size', value: '0.875em' });
|
|
2182
|
+
const lhMap = { '1': '1', sm: '1.25', base: '1.5', lg: '2' };
|
|
2183
|
+
const lhMatch = base.match(/^lh-(1|sm|base|lg)$/);
|
|
2184
|
+
if (lhMatch)
|
|
2185
|
+
declarations.push({ property: 'line-height', value: lhMap[lhMatch[1]] });
|
|
2186
|
+
return declarations;
|
|
2187
|
+
}
|
|
2188
|
+
function bootstrapColorDeclarations(base) {
|
|
2189
|
+
const declarations = [];
|
|
2190
|
+
const bgMatch = base.match(/^bg-(primary|secondary|success|danger|warning|info|light|dark|body|white|transparent)$/);
|
|
2191
|
+
if (bgMatch) {
|
|
2192
|
+
declarations.push({
|
|
2193
|
+
property: 'background-color',
|
|
2194
|
+
value: bgMatch[1] === 'transparent' ? 'transparent' : bootstrapThemeColors[bgMatch[1]],
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
const textMatch = base.match(/^text-(primary|secondary|success|danger|warning|info|light|dark|body|muted|white|black)$/);
|
|
2198
|
+
if (textMatch)
|
|
2199
|
+
declarations.push({ property: 'color', value: bootstrapThemeColors[textMatch[1]] });
|
|
2200
|
+
return declarations;
|
|
2201
|
+
}
|
|
2202
|
+
function bootstrapBorderDeclarations(base) {
|
|
2203
|
+
const declarations = [];
|
|
2204
|
+
if (base === 'border')
|
|
2205
|
+
declarations.push({ property: 'border', value: 'var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)' });
|
|
2206
|
+
if (base === 'border-0')
|
|
2207
|
+
declarations.push({ property: 'border', value: '0' });
|
|
2208
|
+
if (/^border-[1-5]$/.test(base)) {
|
|
2209
|
+
declarations.push({ property: 'border-width', value: `${base.replace('border-', '')}px` });
|
|
2210
|
+
}
|
|
2211
|
+
const borderColor = base.match(/^border-(primary|secondary|success|danger|warning|info|light|dark|white)$/);
|
|
2212
|
+
if (borderColor)
|
|
2213
|
+
declarations.push({ property: 'border-color', value: bootstrapThemeColors[borderColor[1]] });
|
|
2214
|
+
if (base === 'rounded')
|
|
2215
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
|
|
2216
|
+
if (base === 'rounded-0')
|
|
2217
|
+
declarations.push({ property: 'border-radius', value: '0' });
|
|
2218
|
+
if (base === 'rounded-1')
|
|
2219
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-sm)' });
|
|
2220
|
+
if (base === 'rounded-2')
|
|
2221
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
|
|
2222
|
+
if (base === 'rounded-3')
|
|
2223
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-lg)' });
|
|
2224
|
+
if (base === 'rounded-4')
|
|
2225
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xl)' });
|
|
2226
|
+
if (base === 'rounded-5')
|
|
2227
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xxl)' });
|
|
2228
|
+
if (base === 'rounded-pill')
|
|
2229
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-pill)' });
|
|
2230
|
+
if (base === 'rounded-circle')
|
|
2231
|
+
declarations.push({ property: 'border-radius', value: '50%' });
|
|
2232
|
+
if (base === 'shadow')
|
|
2233
|
+
declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow)' });
|
|
2234
|
+
if (base === 'shadow-sm')
|
|
2235
|
+
declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-sm)' });
|
|
2236
|
+
if (base === 'shadow-lg')
|
|
2237
|
+
declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-lg)' });
|
|
2238
|
+
if (base === 'shadow-none')
|
|
2239
|
+
declarations.push({ property: 'box-shadow', value: 'none' });
|
|
2240
|
+
return declarations;
|
|
2241
|
+
}
|
|
2242
|
+
function bootstrapGridDeclarations(base) {
|
|
2243
|
+
if (base === 'col') {
|
|
2244
|
+
return [
|
|
2245
|
+
{ property: 'flex', value: '1 0 0%' },
|
|
2246
|
+
{ property: 'min-width', value: '0' },
|
|
2247
|
+
];
|
|
2248
|
+
}
|
|
2249
|
+
const colMatch = base.match(/^col-(\d{1,2})$/);
|
|
2250
|
+
if (colMatch) {
|
|
2251
|
+
const columns = Math.min(12, Math.max(1, Number(colMatch[1])));
|
|
2252
|
+
const width = `${(columns / 12) * 100}%`;
|
|
2253
|
+
return [
|
|
2254
|
+
{ property: 'flex', value: '0 0 auto' },
|
|
2255
|
+
{ property: 'width', value: width },
|
|
2256
|
+
];
|
|
2257
|
+
}
|
|
2258
|
+
const gapMatch = base.match(/^gap-([0-5])$/);
|
|
2259
|
+
if (gapMatch)
|
|
2260
|
+
return [{ property: 'gap', value: bootstrapSpacingScale[gapMatch[1]] }];
|
|
2261
|
+
return [];
|
|
2262
|
+
}
|
|
2263
|
+
function categorizeResolvedStyleDeclarations(declarations) {
|
|
2264
|
+
const categories = {};
|
|
2265
|
+
for (const declaration of declarations) {
|
|
2266
|
+
const category = resolvedStyleCategory(declaration.property);
|
|
2267
|
+
categories[category] = (0, utils_1.uniqueBy)([...(categories[category] ?? []), declaration], (item) => `${item.className}:${item.property}:${item.value}:${item.source}:${item.selector ?? ''}`).slice(0, 80);
|
|
2268
|
+
}
|
|
2269
|
+
return categories;
|
|
2270
|
+
}
|
|
2271
|
+
function flattenResolvedStyleCategories(categories) {
|
|
2272
|
+
return (0, utils_1.uniqueBy)(Object.values(categories).flat(), (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`);
|
|
2273
|
+
}
|
|
2274
|
+
function createStyleSystemEvidence(classes, declarations, styleContext) {
|
|
2275
|
+
const classSystems = new Map();
|
|
2276
|
+
for (const className of classes) {
|
|
2277
|
+
const systems = new Set();
|
|
2278
|
+
const base = (0, utils_1.classBase)(className).replace(/^!/, '');
|
|
2279
|
+
if (styleContext?.classRules.has(className) ||
|
|
2280
|
+
styleContext?.classRules.has(base) ||
|
|
2281
|
+
styleContext?.classRules.has(base.replace(/^!/, ''))) {
|
|
2282
|
+
systems.add('raw-css');
|
|
2283
|
+
}
|
|
2284
|
+
if (tailwindDeclarationsForClass(className, styleContext).length || isLikelyTailwindClass(className)) {
|
|
2285
|
+
systems.add('tailwind');
|
|
2286
|
+
}
|
|
2287
|
+
if (bootstrapDeclarationsForClass(className, styleContext).length || isBootstrapClass(className)) {
|
|
2288
|
+
systems.add('bootstrap');
|
|
2289
|
+
}
|
|
2290
|
+
if (!systems.size) {
|
|
2291
|
+
systems.add('unknown');
|
|
2292
|
+
}
|
|
2293
|
+
classSystems.set(className, systems);
|
|
2294
|
+
}
|
|
2295
|
+
const observed = (0, utils_1.unique)([
|
|
2296
|
+
...Array.from(classSystems.values()).flatMap((systems) => Array.from(systems)),
|
|
2297
|
+
...declarations.map((declaration) => declaration.styleSystem),
|
|
2298
|
+
]).filter((system) => system !== 'unknown');
|
|
2299
|
+
const unresolvedClasses = classes.filter((className) => classSystems.get(className)?.has('unknown'));
|
|
2300
|
+
const classCounts = Object.fromEntries(['raw-css', 'tailwind', 'bootstrap', 'unknown'].map((system) => [
|
|
2301
|
+
system,
|
|
2302
|
+
Array.from(classSystems.values()).filter((systems) => systems.has(system)).length,
|
|
2303
|
+
]));
|
|
2304
|
+
return omitEmptyRecord({
|
|
2305
|
+
primary: observed.length === 1 ? observed[0] : observed.length > 1 ? 'mixed' : 'unknown',
|
|
2306
|
+
observed,
|
|
2307
|
+
projectDetected: styleContext?.detectedStyleSystems ?? [],
|
|
2308
|
+
classCounts,
|
|
2309
|
+
unresolvedClasses: unresolvedClasses.slice(0, 80),
|
|
2310
|
+
declarationCounts: Object.fromEntries(['raw-css', 'tailwind', 'bootstrap'].map((system) => [
|
|
2311
|
+
system,
|
|
2312
|
+
declarations.filter((declaration) => declaration.styleSystem === system).length,
|
|
2313
|
+
])),
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
function isLikelyTailwindClass(className) {
|
|
2317
|
+
const base = (0, utils_1.classBase)(className).replace(/^!/, '');
|
|
2318
|
+
return Boolean(className.includes(':') ||
|
|
2319
|
+
/\[[^\]]+\]/.test(base) ||
|
|
2320
|
+
/^(?:bg|text|border|rounded|shadow|opacity|blur|backdrop|flex|grid|inline-flex|items|justify|content|self|place|gap|p[trblxy]?|m[trblxy]?|w|h|min-w|min-h|max-w|max-h|size|font|leading|tracking|uppercase|lowercase|capitalize|line-clamp|overflow|whitespace|truncate|relative|absolute|fixed|sticky|inset|top|right|bottom|left|z|col-span|row-span|from|via|to|fill|stroke)-/.test(base));
|
|
2321
|
+
}
|
|
2322
|
+
function resolvedStyleCategory(property) {
|
|
2323
|
+
if (['color', 'background', 'background-color', 'fill', 'stroke'].includes(property)) {
|
|
2324
|
+
return 'color';
|
|
2325
|
+
}
|
|
2326
|
+
if ([
|
|
2327
|
+
'font-size',
|
|
2328
|
+
'font-weight',
|
|
2329
|
+
'line-height',
|
|
2330
|
+
'letter-spacing',
|
|
2331
|
+
'text-transform',
|
|
2332
|
+
'text-align',
|
|
2333
|
+
'font-family',
|
|
2334
|
+
].includes(property)) {
|
|
2335
|
+
return 'typography';
|
|
2336
|
+
}
|
|
2337
|
+
if (/^(?:border|outline|ring)|border-radius/.test(property)) {
|
|
2338
|
+
return 'border';
|
|
2339
|
+
}
|
|
2340
|
+
if (['box-shadow', 'opacity', 'filter', 'backdrop-filter'].includes(property)) {
|
|
2341
|
+
return 'effects';
|
|
2342
|
+
}
|
|
2343
|
+
if (['transition', 'transition-property', 'transition-duration', 'animation', 'transform'].includes(property)) {
|
|
2344
|
+
return 'motion';
|
|
2345
|
+
}
|
|
2346
|
+
if (property === 'display') {
|
|
2347
|
+
return 'display';
|
|
2348
|
+
}
|
|
2349
|
+
if (/^(?:align|justify|place)/.test(property)) {
|
|
2350
|
+
return 'alignment';
|
|
2351
|
+
}
|
|
2352
|
+
if (/^grid/.test(property)) {
|
|
2353
|
+
return 'grid';
|
|
2354
|
+
}
|
|
2355
|
+
if (/^flex/.test(property)) {
|
|
2356
|
+
return 'flex';
|
|
2357
|
+
}
|
|
2358
|
+
if (/^(?:padding|margin|gap|row-gap|column-gap)/.test(property)) {
|
|
2359
|
+
return 'spacing';
|
|
2360
|
+
}
|
|
2361
|
+
if (/^(?:width|height|min-|max-)/.test(property)) {
|
|
2362
|
+
return /^(?:min-|max-)/.test(property) ? 'minMax' : 'sizing';
|
|
2363
|
+
}
|
|
2364
|
+
if (['position', 'inset', 'top', 'right', 'bottom', 'left', 'z-index', 'object-fit'].includes(property)) {
|
|
2365
|
+
return 'position';
|
|
2366
|
+
}
|
|
2367
|
+
if (['overflow', 'overflow-x', 'overflow-y'].includes(property)) {
|
|
2368
|
+
return 'overflow';
|
|
2369
|
+
}
|
|
2370
|
+
if (['white-space', 'overflow-wrap', 'text-overflow', '-webkit-line-clamp'].includes(property)) {
|
|
2371
|
+
return 'textFlow';
|
|
2372
|
+
}
|
|
2373
|
+
return 'layout';
|
|
2374
|
+
}
|
|
2375
|
+
function mergeResolvedStyleCategories(current, next) {
|
|
2376
|
+
const merged = { ...current };
|
|
2377
|
+
for (const [category, declarations] of Object.entries(next)) {
|
|
2378
|
+
const existing = Array.isArray(merged[category])
|
|
2379
|
+
? merged[category]
|
|
2380
|
+
: [];
|
|
2381
|
+
merged[category] = (0, utils_1.uniqueBy)([...existing, ...declarations], (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`).slice(0, 80);
|
|
2382
|
+
}
|
|
2383
|
+
return omitEmptyRecord(merged);
|
|
2384
|
+
}
|
|
2385
|
+
function classBucketRecord(value) {
|
|
2386
|
+
const buckets = {};
|
|
2387
|
+
for (const [key, item] of Object.entries(value)) {
|
|
2388
|
+
if (Array.isArray(item) && item.every((value) => typeof value === 'string')) {
|
|
2389
|
+
buckets[key] = item;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return buckets;
|
|
2393
|
+
}
|
|
2394
|
+
function resolvedStyleRecord(value) {
|
|
2395
|
+
return isRecordValue(value) ? value : {};
|
|
2396
|
+
}
|
|
2397
|
+
function createUiContract(input) {
|
|
2398
|
+
const defaultClasses = (0, utils_1.unique)(input.defaultClasses?.length ? input.defaultClasses : input.classes);
|
|
2399
|
+
const conditionalClasses = (0, utils_1.unique)(input.conditionalClasses ?? []);
|
|
2400
|
+
const decorativeAccentClasses = (0, utils_1.unique)(input.decorativeAccentClasses ?? []);
|
|
2401
|
+
const allClasses = (0, utils_1.unique)([...defaultClasses, ...conditionalClasses, ...decorativeAccentClasses]);
|
|
2402
|
+
const grouped = input.classGroups ?? (0, extractors_1.groupTailwindClasses)(allClasses);
|
|
2403
|
+
const resolvedStyles = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations(allClasses, input.styleContext));
|
|
2404
|
+
const styleSystemEvidence = createStyleSystemEvidence(allClasses, flattenResolvedStyleCategories(resolvedStyles), input.styleContext);
|
|
2405
|
+
const responsiveVariants = createUiResponsiveVariants(allClasses, input.styleContext);
|
|
2406
|
+
const interactionVariants = createUiInteractionVariants(allClasses, input.styleContext);
|
|
2407
|
+
return {
|
|
2408
|
+
identity: omitEmptyRecord({
|
|
2409
|
+
kind: input.kind,
|
|
2410
|
+
tag: input.tag,
|
|
2411
|
+
layoutRole: input.layoutRole,
|
|
2412
|
+
label: input.label,
|
|
2413
|
+
line: input.line,
|
|
2414
|
+
variants: input.variants,
|
|
2415
|
+
props: input.props,
|
|
2416
|
+
styleHelper: input.styleHelper,
|
|
2417
|
+
styleHelperVariants: input.styleHelperVariants,
|
|
2418
|
+
}),
|
|
2419
|
+
semantics: omitEmptyRecord({
|
|
2420
|
+
kind: input.kind,
|
|
2421
|
+
role: input.semanticRole ?? input.layoutRole,
|
|
2422
|
+
semanticRole: input.semanticRole,
|
|
2423
|
+
semanticProfile: input.semanticProfile,
|
|
2424
|
+
roleSignature: input.roleSignature,
|
|
2425
|
+
}),
|
|
2426
|
+
visual: omitEmptyRecord({
|
|
2427
|
+
color: grouped.color ?? [],
|
|
2428
|
+
typography: grouped.typography ?? [],
|
|
2429
|
+
border: grouped.border ?? [],
|
|
2430
|
+
effects: grouped.effects ?? [],
|
|
2431
|
+
motion: grouped.motion ?? [],
|
|
2432
|
+
decorativeAccentClasses,
|
|
2433
|
+
resolvedStyles: omitEmptyRecord({
|
|
2434
|
+
color: resolvedStyles.color,
|
|
2435
|
+
typography: resolvedStyles.typography,
|
|
2436
|
+
border: resolvedStyles.border,
|
|
2437
|
+
effects: resolvedStyles.effects,
|
|
2438
|
+
motion: resolvedStyles.motion,
|
|
2439
|
+
}),
|
|
2440
|
+
}),
|
|
2441
|
+
layout: omitEmptyRecord({
|
|
2442
|
+
display: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|grid|inline-grid|block|inline|inline-block|hidden|contents|table)$/.test((0, utils_1.classBase)(className))),
|
|
2443
|
+
alignment: filterUiContractClasses(allClasses, (className) => /^(?:items-|justify-|content-|self-|place-)/.test((0, utils_1.classBase)(className))),
|
|
2444
|
+
grid: filterUiContractClasses(allClasses, (className) => /^(?:grid|grid-cols-|grid-rows-|auto-rows-|auto-cols-|col-|row-)/.test((0, utils_1.classBase)(className))),
|
|
2445
|
+
flex: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|flex-|basis-|grow|shrink)/.test((0, utils_1.classBase)(className))),
|
|
2446
|
+
spacing: grouped.spacing ?? [],
|
|
2447
|
+
sizing: grouped.sizing ?? [],
|
|
2448
|
+
position: grouped.position ?? [],
|
|
2449
|
+
layout: grouped.layout ?? [],
|
|
2450
|
+
other: grouped.other ?? [],
|
|
2451
|
+
resolvedStyles: omitEmptyRecord({
|
|
2452
|
+
display: resolvedStyles.display,
|
|
2453
|
+
alignment: resolvedStyles.alignment,
|
|
2454
|
+
grid: resolvedStyles.grid,
|
|
2455
|
+
flex: resolvedStyles.flex,
|
|
2456
|
+
spacing: resolvedStyles.spacing,
|
|
2457
|
+
sizing: resolvedStyles.sizing,
|
|
2458
|
+
position: resolvedStyles.position,
|
|
2459
|
+
layout: resolvedStyles.layout,
|
|
2460
|
+
}),
|
|
2461
|
+
}),
|
|
2462
|
+
responsiveVariants,
|
|
2463
|
+
interactionVariants,
|
|
2464
|
+
safetyRules: omitEmptyRecord({
|
|
2465
|
+
layoutSafety: input.layoutSafety,
|
|
2466
|
+
scaleProfile: input.scaleProfile,
|
|
2467
|
+
roleConstraints: input.roleSignature
|
|
2468
|
+
? omitEmptyRecord({
|
|
2469
|
+
preserveRole: input.roleSignature.role,
|
|
2470
|
+
preserveRoleGroup: input.roleSignature.roleGroup,
|
|
2471
|
+
preserveScale: input.roleSignature.scale,
|
|
2472
|
+
preserveDensity: input.roleSignature.density,
|
|
2473
|
+
preserveSurface: input.roleSignature.surface,
|
|
2474
|
+
preserveLayout: input.roleSignature.layout,
|
|
2475
|
+
exactClassFacts: input.roleSignature.exactClassFacts,
|
|
2476
|
+
flags: input.roleSignature.flags,
|
|
2477
|
+
})
|
|
2478
|
+
: undefined,
|
|
2479
|
+
resolvedStyles: omitEmptyRecord({
|
|
2480
|
+
textFlow: resolvedStyles.textFlow,
|
|
2481
|
+
overflow: resolvedStyles.overflow,
|
|
2482
|
+
minMax: resolvedStyles.minMax,
|
|
2483
|
+
}),
|
|
2484
|
+
}),
|
|
2485
|
+
sourceEvidence: omitEmptyRecord({
|
|
2486
|
+
extractor: 'ui.contract',
|
|
2487
|
+
path: input.path,
|
|
2488
|
+
line: input.line,
|
|
2489
|
+
sourceHash: input.sourceHash,
|
|
2490
|
+
confidence: input.confidence,
|
|
2491
|
+
evidence: input.evidence,
|
|
2492
|
+
classSource: input.classSource,
|
|
2493
|
+
styleSystems: styleSystemEvidence,
|
|
2494
|
+
}),
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
function createUiContractForElement(element, file, confidence, styleContext) {
|
|
2498
|
+
return createUiContract({
|
|
2499
|
+
kind: element.kind,
|
|
2500
|
+
tag: element.originalTag,
|
|
2501
|
+
layoutRole: element.layoutRole,
|
|
2502
|
+
label: element.label,
|
|
2503
|
+
line: element.line,
|
|
2504
|
+
path: file?.relPath,
|
|
2505
|
+
sourceHash: file?.sourceHash,
|
|
2506
|
+
confidence,
|
|
2507
|
+
evidence: element.evidence,
|
|
2508
|
+
classSource: element.classSource,
|
|
2509
|
+
classes: element.classes,
|
|
2510
|
+
defaultClasses: element.defaultClasses,
|
|
2511
|
+
conditionalClasses: element.conditionalClasses,
|
|
2512
|
+
decorativeAccentClasses: element.decorativeAccentClasses,
|
|
2513
|
+
classGroups: element.classGroups,
|
|
2514
|
+
layoutSafety: element.layoutSafety,
|
|
2515
|
+
responsiveProfile: element.responsiveProfile,
|
|
2516
|
+
scaleProfile: element.scaleProfile,
|
|
2517
|
+
semanticProfile: element.semanticProfile,
|
|
2518
|
+
roleSignature: element.roleSignature,
|
|
2519
|
+
variants: element.variants,
|
|
2520
|
+
props: element.props,
|
|
2521
|
+
styleHelper: element.styleHelper,
|
|
2522
|
+
styleHelperVariants: element.styleHelperVariants,
|
|
2523
|
+
styleContext,
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
function createUiContractForChild(child, styleContext) {
|
|
2527
|
+
return createUiContract({
|
|
2528
|
+
kind: child.kind,
|
|
2529
|
+
tag: child.originalTag,
|
|
2530
|
+
layoutRole: child.layoutRole,
|
|
2531
|
+
semanticRole: child.semanticRole,
|
|
2532
|
+
label: child.label,
|
|
2533
|
+
classes: child.classes,
|
|
2534
|
+
defaultClasses: child.defaultClasses,
|
|
2535
|
+
conditionalClasses: child.conditionalClasses,
|
|
2536
|
+
layoutSafety: child.layoutSafety,
|
|
2537
|
+
responsiveProfile: child.responsiveProfile,
|
|
2538
|
+
scaleProfile: child.scaleProfile,
|
|
2539
|
+
semanticProfile: child.semanticProfile,
|
|
2540
|
+
roleSignature: child.roleSignature,
|
|
2541
|
+
variants: child.variants,
|
|
2542
|
+
props: child.props,
|
|
2543
|
+
styleHelper: child.styleHelper,
|
|
2544
|
+
styleHelperVariants: child.styleHelperVariants,
|
|
2545
|
+
styleContext,
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
function createUiPatternContract(input) {
|
|
2549
|
+
const contract = createUiContract(input);
|
|
2550
|
+
return {
|
|
2551
|
+
...contract,
|
|
2552
|
+
identity: omitEmptyRecord({
|
|
2553
|
+
...contract.identity,
|
|
2554
|
+
name: input.name,
|
|
2555
|
+
count: input.count,
|
|
2556
|
+
}),
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
function createUiRelationships(input) {
|
|
2560
|
+
return {
|
|
2561
|
+
...(input.parentContext ? { parentContext: input.parentContext } : {}),
|
|
2562
|
+
...(input.childStructureContract
|
|
2563
|
+
? { childStructureContract: input.childStructureContract }
|
|
2564
|
+
: {}),
|
|
2565
|
+
...(input.compoundStructure ? { compoundStructure: input.compoundStructure } : {}),
|
|
2566
|
+
graphKeys: input.graphKeys ?? [],
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
function createUiResponsiveVariants(classes, styleContext) {
|
|
2570
|
+
const variants = {};
|
|
2571
|
+
for (const className of classes) {
|
|
2572
|
+
const responsivePrefixes = responsivePrefixesForClass(className);
|
|
2573
|
+
for (const responsivePrefix of responsivePrefixes) {
|
|
2574
|
+
variants[responsivePrefix] = mergeUiContractClassBuckets(classBucketRecord(variants[responsivePrefix] ?? {}), uiContractClassBucket(className));
|
|
2575
|
+
const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
|
|
2576
|
+
variants[responsivePrefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[responsivePrefix].resolvedStyles), resolved);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
return variants;
|
|
2580
|
+
}
|
|
2581
|
+
function createUiInteractionVariants(classes, styleContext) {
|
|
2582
|
+
const variants = {};
|
|
2583
|
+
for (const className of classes) {
|
|
2584
|
+
const prefixes = className.split(':').slice(0, -1);
|
|
2585
|
+
const statePrefixes = prefixes.filter(isInteractionPrefix);
|
|
2586
|
+
for (const prefix of statePrefixes) {
|
|
2587
|
+
variants[prefix] = mergeUiContractClassBuckets(classBucketRecord(variants[prefix] ?? {}), uiContractClassBucket(className));
|
|
2588
|
+
const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
|
|
2589
|
+
variants[prefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[prefix].resolvedStyles), resolved);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return variants;
|
|
2593
|
+
}
|
|
2594
|
+
function uiContractClassBucket(className) {
|
|
2595
|
+
const base = (0, utils_1.classBase)(className);
|
|
2596
|
+
if ((0, extractors_1.isLayoutClass)(className)) {
|
|
2597
|
+
return { layout: [className] };
|
|
2598
|
+
}
|
|
2599
|
+
if ((0, extractors_1.isSpacingClass)(className)) {
|
|
2600
|
+
return { spacing: [className] };
|
|
2601
|
+
}
|
|
2602
|
+
if ((0, extractors_1.isSizingClass)(className)) {
|
|
2603
|
+
return { sizing: [className] };
|
|
2604
|
+
}
|
|
2605
|
+
if ((0, extractors_1.isTypographyClass)(className)) {
|
|
2606
|
+
return { typography: [className] };
|
|
2607
|
+
}
|
|
2608
|
+
if (/^(?:bg-|from-|via-|to-|text-|decoration-|accent-|caret-|fill-|stroke-)/.test(base)) {
|
|
2609
|
+
return { color: [className] };
|
|
2610
|
+
}
|
|
2611
|
+
if (/^(?:rounded|border|divide-|outline|ring)/.test(base)) {
|
|
2612
|
+
return { border: [className] };
|
|
2613
|
+
}
|
|
2614
|
+
if (/^(?:shadow|opacity|blur|backdrop|mix-|bg-blend|drop-shadow|filter)/.test(base)) {
|
|
2615
|
+
return { effects: [className] };
|
|
2616
|
+
}
|
|
2617
|
+
if (/^(?:absolute|relative|fixed|sticky|z-|overflow-|object-)/.test(base)) {
|
|
2618
|
+
return { position: [className] };
|
|
2619
|
+
}
|
|
2620
|
+
if (/^(?:transition|duration|ease|delay|animate-|transform|scale-|rotate|translate-)/.test(base)) {
|
|
2621
|
+
return { motion: [className] };
|
|
2622
|
+
}
|
|
2623
|
+
return { other: [className] };
|
|
2624
|
+
}
|
|
2625
|
+
function mergeUiContractClassBuckets(current, next) {
|
|
2626
|
+
const merged = { ...current };
|
|
2627
|
+
for (const [bucket, classes] of Object.entries(next)) {
|
|
2628
|
+
merged[bucket] = (0, utils_1.unique)([...(merged[bucket] ?? []), ...classes]).slice(0, 120);
|
|
2629
|
+
}
|
|
2630
|
+
return merged;
|
|
2631
|
+
}
|
|
2632
|
+
function filterUiContractClasses(classes, predicate) {
|
|
2633
|
+
return (0, utils_1.unique)(classes.filter(predicate)).slice(0, 120);
|
|
2634
|
+
}
|
|
2635
|
+
function isResponsivePrefix(prefix) {
|
|
2636
|
+
return /^(?:sm|md|lg|xl|2xl|min-|max-)/.test(prefix);
|
|
2637
|
+
}
|
|
2638
|
+
function responsivePrefixesForClass(className) {
|
|
2639
|
+
const prefixes = className.split(':').slice(0, -1).filter(isResponsivePrefix);
|
|
2640
|
+
const bootstrapPrefixes = bootstrapResponsivePrefixes(className);
|
|
2641
|
+
return (0, utils_1.unique)([...prefixes, ...bootstrapPrefixes]);
|
|
2642
|
+
}
|
|
2643
|
+
function isInteractionPrefix(prefix) {
|
|
2644
|
+
return /^(?:hover|focus|focus-visible|focus-within|active|disabled|visited|checked|selected|open|dark|group|group-hover|peer|peer-hover|aria|data)/.test(prefix);
|
|
2645
|
+
}
|
|
2646
|
+
function omitEmptyRecord(value) {
|
|
2647
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => {
|
|
2648
|
+
if (item === undefined || item === null) {
|
|
2649
|
+
return false;
|
|
2650
|
+
}
|
|
2651
|
+
if (Array.isArray(item)) {
|
|
2652
|
+
return item.length > 0;
|
|
2653
|
+
}
|
|
2654
|
+
if (typeof item === 'object') {
|
|
2655
|
+
return Object.keys(item).length > 0;
|
|
2656
|
+
}
|
|
2657
|
+
return true;
|
|
2658
|
+
}));
|
|
2659
|
+
}
|
|
2660
|
+
function mergeLayoutSafetyProfiles(items) {
|
|
2661
|
+
const profiles = items.map((item) => item.layoutSafety).filter(isLayoutSafetyProfile);
|
|
2662
|
+
if (!profiles.length) {
|
|
2663
|
+
return undefined;
|
|
2664
|
+
}
|
|
2665
|
+
const merged = {
|
|
2666
|
+
wrapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.wrapClasses)).slice(0, 80),
|
|
2667
|
+
overflowClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.overflowClasses)).slice(0, 80),
|
|
2668
|
+
minSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.minSizeClasses)).slice(0, 80),
|
|
2669
|
+
maxSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxSizeClasses)).slice(0, 80),
|
|
2670
|
+
fitClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.fitClasses)).slice(0, 80),
|
|
2671
|
+
flexBehaviorClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.flexBehaviorClasses)).slice(0, 80),
|
|
2672
|
+
layoutSafetyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutSafetyClasses)).slice(0, 120),
|
|
2673
|
+
};
|
|
2674
|
+
return merged.layoutSafetyClasses.length ? merged : undefined;
|
|
2675
|
+
}
|
|
2676
|
+
function mergeResponsiveProfiles(items) {
|
|
2677
|
+
const profiles = items.map((item) => item.responsiveProfile).filter(isResponsiveProfile);
|
|
2678
|
+
if (!profiles.length) {
|
|
2679
|
+
return undefined;
|
|
2680
|
+
}
|
|
2681
|
+
const breakpoints = {};
|
|
2682
|
+
for (const profile of profiles) {
|
|
2683
|
+
for (const [breakpoint, classes] of Object.entries(profile.breakpoints)) {
|
|
2684
|
+
breakpoints[breakpoint] = (0, utils_1.unique)([...(breakpoints[breakpoint] ?? []), ...classes]).slice(0, 80);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
const merged = {
|
|
2688
|
+
breakpoints,
|
|
2689
|
+
layoutClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutClasses)).slice(0, 120),
|
|
2690
|
+
typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
|
|
2691
|
+
spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
|
|
2692
|
+
sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
|
|
2693
|
+
visibilityClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.visibilityClasses)).slice(0, 120),
|
|
2694
|
+
stateClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.stateClasses)).slice(0, 120),
|
|
2695
|
+
};
|
|
2696
|
+
return Object.keys(merged.breakpoints).length ? merged : undefined;
|
|
2697
|
+
}
|
|
2698
|
+
function mergeScaleProfiles(items) {
|
|
2699
|
+
const profiles = items.map((item) => item.scaleProfile).filter(isScaleProfile);
|
|
2700
|
+
if (!profiles.length) {
|
|
2701
|
+
return undefined;
|
|
2702
|
+
}
|
|
2703
|
+
const merged = {
|
|
2704
|
+
typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
|
|
2705
|
+
spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
|
|
2706
|
+
sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
|
|
2707
|
+
gapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.gapClasses)).slice(0, 80),
|
|
2708
|
+
maxWidthClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxWidthClasses)).slice(0, 80),
|
|
2709
|
+
lineHeightClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.lineHeightClasses)).slice(0, 80),
|
|
2710
|
+
};
|
|
2711
|
+
return Object.values(merged).some((classes) => classes.length) ? merged : undefined;
|
|
2712
|
+
}
|
|
2713
|
+
function mergeSemanticProfiles(items) {
|
|
2714
|
+
const profiles = items.map((item) => item.semanticProfile).filter(isSemanticProfile);
|
|
2715
|
+
if (!profiles.length) {
|
|
2716
|
+
return undefined;
|
|
659
2717
|
}
|
|
2718
|
+
return {
|
|
2719
|
+
roles: (0, utils_1.unique)(profiles.flatMap((profile) => profile.roles)).slice(0, 40),
|
|
2720
|
+
isCompound: profiles.some((profile) => profile.isCompound),
|
|
2721
|
+
hasIcon: profiles.some((profile) => profile.hasIcon),
|
|
2722
|
+
hasDivider: profiles.some((profile) => profile.hasDivider),
|
|
2723
|
+
hasImage: profiles.some((profile) => profile.hasImage),
|
|
2724
|
+
hasMultipleTextStyles: profiles.some((profile) => profile.hasMultipleTextStyles),
|
|
2725
|
+
textStyleCount: Math.max(...profiles.map((profile) => profile.textStyleCount)),
|
|
2726
|
+
childCount: Math.max(...profiles.map((profile) => profile.childCount)),
|
|
2727
|
+
};
|
|
660
2728
|
}
|
|
661
|
-
function
|
|
662
|
-
return (
|
|
2729
|
+
function summarizePatternLayoutSafety(elements) {
|
|
2730
|
+
return mergeLayoutSafetyProfiles([
|
|
2731
|
+
...elements,
|
|
2732
|
+
...elements.flatMap((element) => element.childSummary ?? []),
|
|
2733
|
+
]);
|
|
663
2734
|
}
|
|
664
|
-
function
|
|
665
|
-
return (
|
|
2735
|
+
function summarizePatternResponsiveProfile(elements) {
|
|
2736
|
+
return mergeResponsiveProfiles([
|
|
2737
|
+
...elements,
|
|
2738
|
+
...elements.flatMap((element) => element.childSummary ?? []),
|
|
2739
|
+
]);
|
|
666
2740
|
}
|
|
667
|
-
function
|
|
2741
|
+
function summarizePatternScaleProfile(elements) {
|
|
2742
|
+
return mergeScaleProfiles([
|
|
2743
|
+
...elements,
|
|
2744
|
+
...elements.flatMap((element) => element.childSummary ?? []),
|
|
2745
|
+
]);
|
|
2746
|
+
}
|
|
2747
|
+
function summarizePatternSemanticProfile(elements) {
|
|
2748
|
+
return mergeSemanticProfiles(elements);
|
|
2749
|
+
}
|
|
2750
|
+
function summarizePatternRoleSignatures(elements) {
|
|
2751
|
+
return (0, utils_1.uniqueBy)(elements
|
|
2752
|
+
.map((element) => element.roleSignature)
|
|
2753
|
+
.filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
|
|
2754
|
+
}
|
|
2755
|
+
function summarizeParentContexts(elements) {
|
|
2756
|
+
const elementsWithParent = elements.filter((element) => element.parentClasses?.length);
|
|
2757
|
+
const grouped = (0, utils_1.groupBy)(elementsWithParent, (element) => [
|
|
2758
|
+
element.parentKind ?? 'unknown-parent',
|
|
2759
|
+
element.parentTag ?? 'unknown-tag',
|
|
2760
|
+
JSON.stringify(element.parentClassGroups ?? (0, extractors_1.groupTailwindClasses)(element.parentClasses ?? [])),
|
|
2761
|
+
].join(':'));
|
|
2762
|
+
return Array.from(grouped.values())
|
|
2763
|
+
.map((group) => {
|
|
2764
|
+
const first = group[0];
|
|
2765
|
+
const parentClasses = (0, utils_1.unique)(group.flatMap((element) => element.parentClasses ?? [])).slice(0, 80);
|
|
2766
|
+
return {
|
|
2767
|
+
parentKind: first?.parentKind,
|
|
2768
|
+
parentTag: first?.parentTag,
|
|
2769
|
+
count: group.length,
|
|
2770
|
+
parentClasses,
|
|
2771
|
+
parentClassGroups: (0, extractors_1.mergeClassGroups)(group
|
|
2772
|
+
.map((element) => element.parentClassGroups)
|
|
2773
|
+
.filter((value) => Boolean(value))),
|
|
2774
|
+
childKinds: (0, utils_1.unique)(group.map((element) => element.kind)).slice(0, 24),
|
|
2775
|
+
layoutRoles: (0, utils_1.unique)(group
|
|
2776
|
+
.map((element) => element.layoutRole)
|
|
2777
|
+
.filter((value) => Boolean(value))).slice(0, 24),
|
|
2778
|
+
};
|
|
2779
|
+
})
|
|
2780
|
+
.sort((a, b) => Number(b.count) - Number(a.count))
|
|
2781
|
+
.slice(0, 12);
|
|
2782
|
+
}
|
|
2783
|
+
function summarizeElementChildContract(children, styleContext) {
|
|
2784
|
+
if (!children.length) {
|
|
2785
|
+
return undefined;
|
|
2786
|
+
}
|
|
668
2787
|
return {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
2788
|
+
directChildCount: children.length,
|
|
2789
|
+
childOrder: children.slice(0, 12).map((child) => ({
|
|
2790
|
+
kind: child.kind,
|
|
2791
|
+
tag: child.originalTag,
|
|
2792
|
+
layoutRole: child.layoutRole,
|
|
2793
|
+
semanticRole: child.semanticRole,
|
|
2794
|
+
roleSignature: child.roleSignature,
|
|
2795
|
+
labelHint: child.label ? labelSignature(child.label) : undefined,
|
|
2796
|
+
label: child.label,
|
|
2797
|
+
uiContract: createUiContractForChild(child, styleContext),
|
|
2798
|
+
defaultClasses: (child.defaultClasses?.length ? child.defaultClasses : child.classes).slice(0, 48),
|
|
2799
|
+
layoutSafety: child.layoutSafety,
|
|
2800
|
+
scaleProfile: child.scaleProfile,
|
|
2801
|
+
classGroups: (0, extractors_1.groupTailwindClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes),
|
|
2802
|
+
})),
|
|
2803
|
+
childKinds: (0, utils_1.unique)(children.map((child) => child.kind)).slice(0, 24),
|
|
2804
|
+
semanticRoles: (0, utils_1.unique)(children
|
|
2805
|
+
.map((child) => child.semanticRole)
|
|
2806
|
+
.filter((value) => Boolean(value))).slice(0, 24),
|
|
2807
|
+
layoutRoles: (0, utils_1.unique)(children.map((child) => child.layoutRole).filter((value) => Boolean(value))).slice(0, 24),
|
|
674
2808
|
};
|
|
675
2809
|
}
|
|
676
|
-
function
|
|
2810
|
+
function summarizePatternChildStructure(elements, styleContext) {
|
|
2811
|
+
const childSummaries = elements
|
|
2812
|
+
.map((element) => element.childSummary ?? [])
|
|
2813
|
+
.filter((children) => children.length);
|
|
2814
|
+
if (!childSummaries.length) {
|
|
2815
|
+
return undefined;
|
|
2816
|
+
}
|
|
2817
|
+
const childCounts = childSummaries.map((children) => children.length);
|
|
2818
|
+
const childOrderExamples = (0, utils_1.uniqueBy)(childSummaries
|
|
2819
|
+
.map((children) => summarizeElementChildContract(children, styleContext))
|
|
2820
|
+
.filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
|
|
677
2821
|
return {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
source: sourceInfo(extractor, 'high', [`path:${relPath}`], file),
|
|
2822
|
+
preserveDirectChildRelationships: true,
|
|
2823
|
+
directChildCountRange: {
|
|
2824
|
+
min: Math.min(...childCounts),
|
|
2825
|
+
max: Math.max(...childCounts),
|
|
2826
|
+
},
|
|
2827
|
+
childKinds: (0, utils_1.unique)(childSummaries.flatMap((children) => children.map((child) => child.kind))).slice(0, 32),
|
|
2828
|
+
childOrderExamples,
|
|
686
2829
|
};
|
|
687
2830
|
}
|
|
688
|
-
function
|
|
2831
|
+
function summarizeCompoundStructure(elements, styleContext) {
|
|
2832
|
+
const compoundElements = elements.filter((element) => element.semanticProfile?.isCompound);
|
|
2833
|
+
if (!compoundElements.length) {
|
|
2834
|
+
return undefined;
|
|
2835
|
+
}
|
|
2836
|
+
const childCounts = compoundElements.map((element) => element.childSummary?.length ?? 0);
|
|
2837
|
+
const childOrderExamples = (0, utils_1.uniqueBy)(compoundElements
|
|
2838
|
+
.map((element) => summarizeElementChildContract(element.childSummary ?? [], styleContext))
|
|
2839
|
+
.filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
|
|
2840
|
+
const semanticProfile = summarizePatternSemanticProfile(compoundElements);
|
|
689
2841
|
return {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
2842
|
+
preserveCompoundChildren: true,
|
|
2843
|
+
preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
|
|
2844
|
+
preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
|
|
2845
|
+
directChildCountRange: {
|
|
2846
|
+
min: Math.min(...childCounts),
|
|
2847
|
+
max: Math.max(...childCounts),
|
|
2848
|
+
},
|
|
2849
|
+
semanticRoles: semanticProfile?.roles ?? [],
|
|
2850
|
+
childOrderExamples,
|
|
695
2851
|
};
|
|
696
2852
|
}
|
|
697
|
-
function
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
2853
|
+
function createUiGenerationConstraints(kind, elements, decorativeAccentClasses) {
|
|
2854
|
+
const layoutSafety = summarizePatternLayoutSafety(elements);
|
|
2855
|
+
const responsiveProfile = summarizePatternResponsiveProfile(elements);
|
|
2856
|
+
const scaleProfile = summarizePatternScaleProfile(elements);
|
|
2857
|
+
const roleSignatures = summarizePatternRoleSignatures(elements);
|
|
2858
|
+
const parentContexts = summarizeParentContexts(elements);
|
|
2859
|
+
const childStructure = summarizePatternChildStructure(elements);
|
|
2860
|
+
const compoundStructure = summarizeCompoundStructure(elements);
|
|
2861
|
+
return {
|
|
2862
|
+
preserveRootAndChildrenTogether: true,
|
|
2863
|
+
preserveParentLayoutContext: Boolean(parentContexts.length),
|
|
2864
|
+
preserveDirectChildRelationships: Boolean(childStructure),
|
|
2865
|
+
preserveCompoundChildRoles: Boolean(compoundStructure),
|
|
2866
|
+
avoidUnobservedChildren: ['card', 'form', 'nav', 'section', 'table', 'dialog'].includes(kind) ||
|
|
2867
|
+
Boolean(compoundStructure),
|
|
2868
|
+
preserveResponsiveBreakpoints: Boolean(responsiveProfile),
|
|
2869
|
+
preserveLayoutSafety: Boolean(layoutSafety),
|
|
2870
|
+
preserveScaleBounds: Boolean(scaleProfile),
|
|
2871
|
+
preserveRoleSignatures: Boolean(roleSignatures.length),
|
|
2872
|
+
avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
function createUiGenerationGuidance(kind, elements) {
|
|
2876
|
+
const guidance = [];
|
|
2877
|
+
const elementClasses = new Set(elements.flatMap((element) => [
|
|
2878
|
+
...element.classes,
|
|
2879
|
+
...(element.defaultClasses ?? []),
|
|
2880
|
+
...(element.conditionalClasses ?? []),
|
|
2881
|
+
...(element.parentClasses ?? []),
|
|
2882
|
+
]));
|
|
2883
|
+
if (kind === 'card') {
|
|
2884
|
+
guidance.push('Use rootDefaultClasses for the outer card shell. Use elementPatterns for nested panels, slots, bullets, and footer layout instead of merging every class onto the root.', 'Preserve the observed child structure for cards. Do not add decorative children, progress bars, icons, or extra badges unless those children exist in childStructure or the task explicitly asks for them.');
|
|
704
2885
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
2886
|
+
if (kind === 'button') {
|
|
2887
|
+
guidance.push('Choose button variants by actionUsages/variantRoles. Ghost and link-style actions are low-emphasis; do not use them for primary CTAs or form submits.', 'Preserve button display, height, padding, no-wrap, and shrink/min-width classes together so labels do not wrap or collapse.');
|
|
2888
|
+
}
|
|
2889
|
+
if (kind === 'badge' && summarizeCompoundStructure(elements)) {
|
|
2890
|
+
guidance.push('Compound badges, hero eyebrows, and brand chips are connected lockups. Preserve icon/image children, dividers, text segments, child order, compact spacing, and separate text styles instead of collapsing them into one text-only pill.');
|
|
2891
|
+
}
|
|
2892
|
+
if (kind === 'heading') {
|
|
2893
|
+
guidance.push('Preserve observed heading scale, max-width, line-height, and responsive typography classes. Do not upscale headings beyond the captured scaleProfile.');
|
|
2894
|
+
}
|
|
2895
|
+
if (['section', 'layout', 'form', 'nav'].includes(kind)) {
|
|
2896
|
+
guidance.push('Preserve parent grid/flex context, responsive breakpoints, and child order as layout contracts instead of recreating the section from isolated tokens.');
|
|
2897
|
+
}
|
|
2898
|
+
if (elements.some((element) => element.conditionalClasses?.length)) {
|
|
2899
|
+
guidance.push('conditionalClasses describe state or variant changes; apply them only when the matching condition, selected state, or variant is present.');
|
|
2900
|
+
}
|
|
2901
|
+
if (elementClasses.has('section-frame') || elementClasses.has('sm:section-frame')) {
|
|
2902
|
+
guidance.push('section-frame is a framed surface paint model with its own clipped section-frame::before grid overlay; preserve which element owns that frame.');
|
|
2903
|
+
}
|
|
2904
|
+
if (elementClasses.has('section-frame') && elementClasses.has('context-surface')) {
|
|
2905
|
+
guidance.push('When section-frame and context-surface appear together, keep the pairing so direct children remain above the parent grid overlay.');
|
|
2906
|
+
}
|
|
2907
|
+
if (elements.some((element) => (element.childSummary ?? []).some((child) => (child.defaultClasses ?? child.classes).some((className) => className === 'section-frame')))) {
|
|
2908
|
+
guidance.push('Nested framed child surfaces need their own section-frame/context-surface classes when the source design gives each child a separate grid origin.');
|
|
2909
|
+
}
|
|
2910
|
+
if (summarizePatternLayoutSafety(elements)) {
|
|
2911
|
+
guidance.push('layoutSafety records wrapping, overflow, min/max size, and flex behavior that prevent text collision and button/card overflow. Keep those classes with the element that owns them.');
|
|
2912
|
+
}
|
|
2913
|
+
if (summarizeParentContexts(elements).length) {
|
|
2914
|
+
guidance.push('parentContexts records the grid/flex surface that owns this pattern. Preserve that parent relationship when generating repeated cards or nested panels.');
|
|
2915
|
+
}
|
|
2916
|
+
if (summarizePatternResponsiveProfile(elements)) {
|
|
2917
|
+
guidance.push('responsiveProfile is part of the contract. Keep mobile, tablet, and desktop variants attached to the same elements as the source.');
|
|
2918
|
+
}
|
|
2919
|
+
if (summarizePatternRoleSignatures(elements).length) {
|
|
2920
|
+
guidance.push('roleSignatures are part of the contract. Similar-looking UI with different role, scale, density, surface, layout, or exactClassFacts is a different generation target.');
|
|
2921
|
+
}
|
|
2922
|
+
return guidance;
|
|
2923
|
+
}
|
|
2924
|
+
function summarizeActionUsages(elements) {
|
|
2925
|
+
return elements.slice(0, 80).flatMap((element) => {
|
|
2926
|
+
const variantNames = actionVariantNamesForElement(element);
|
|
2927
|
+
return variantNames.map((variantName) => ({
|
|
2928
|
+
label: element.label,
|
|
2929
|
+
tag: element.originalTag,
|
|
2930
|
+
line: element.line,
|
|
2931
|
+
variant: variantName,
|
|
2932
|
+
size: element.variants?.size,
|
|
2933
|
+
type: element.variants?.type ?? element.type,
|
|
2934
|
+
styleHelper: element.styleHelper,
|
|
2935
|
+
parentKind: element.parentKind,
|
|
2936
|
+
parentLayoutRole: element.parentKind ? element.layoutRole : undefined,
|
|
2937
|
+
...inferActionVariantRole(variantName, [
|
|
2938
|
+
...(element.defaultClasses ?? []),
|
|
2939
|
+
...(element.classes ?? []),
|
|
2940
|
+
]),
|
|
2941
|
+
}));
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
function actionVariantNamesForElement(element) {
|
|
2945
|
+
const variantValue = element.variants?.variant ?? element.styleHelperVariants?.variant;
|
|
2946
|
+
if (!variantValue && /button/i.test(element.styleHelper ?? '')) {
|
|
2947
|
+
return ['default'];
|
|
718
2948
|
}
|
|
2949
|
+
if (!variantValue) {
|
|
2950
|
+
return ['custom'];
|
|
2951
|
+
}
|
|
2952
|
+
const quotedValues = Array.from(variantValue.matchAll(/["']([^"']+)["']/g)).map((match) => match[1]);
|
|
2953
|
+
if (quotedValues.length) {
|
|
2954
|
+
return (0, utils_1.unique)(quotedValues).slice(0, 6);
|
|
2955
|
+
}
|
|
2956
|
+
return (0, utils_1.unique)(variantValue
|
|
2957
|
+
.split('|')
|
|
2958
|
+
.map((value) => value.trim())
|
|
2959
|
+
.filter(Boolean)).slice(0, 6);
|
|
719
2960
|
}
|
|
720
|
-
function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
2961
|
+
function addUiPatternRules(entries, usedKeys, nodes, edges, files, styleContext) {
|
|
721
2962
|
for (const file of files) {
|
|
2963
|
+
if ((0, extractors_1.isTestFile)(file.relPath)) {
|
|
2964
|
+
continue;
|
|
2965
|
+
}
|
|
722
2966
|
const elementsByKind = (0, utils_1.groupBy)(file.uiElements, (element) => element.kind);
|
|
723
2967
|
for (const [kind, elements] of elementsByKind) {
|
|
724
2968
|
if (!elements.length || kind === 'unknown' || kind === 'link') {
|
|
@@ -734,6 +2978,62 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
734
2978
|
const decorativeAccentClasses = (0, extractors_1.filterClasses)(classes, extractors_1.isDecorativeAccentClass).slice(0, 40);
|
|
735
2979
|
const classGroups = (0, extractors_1.mergeClassGroups)(elements.map((element) => element.classGroups ?? {}));
|
|
736
2980
|
const variants = (0, extractors_1.mergeVariantProps)(elements.map((element) => element.variants ?? {}));
|
|
2981
|
+
const elementPatterns = summarizeUiElementPatterns(elements, styleContext);
|
|
2982
|
+
const layoutSafety = summarizePatternLayoutSafety(elements);
|
|
2983
|
+
const responsiveProfile = summarizePatternResponsiveProfile(elements);
|
|
2984
|
+
const scaleProfile = summarizePatternScaleProfile(elements);
|
|
2985
|
+
const semanticProfile = summarizePatternSemanticProfile(elements);
|
|
2986
|
+
const roleSignatures = summarizePatternRoleSignatures(elements);
|
|
2987
|
+
const parentContexts = summarizeParentContexts(elements);
|
|
2988
|
+
const childStructureContract = summarizePatternChildStructure(elements, styleContext);
|
|
2989
|
+
const compoundStructure = summarizeCompoundStructure(elements, styleContext);
|
|
2990
|
+
const generationConstraints = createUiGenerationConstraints(kind, elements, decorativeAccentClasses);
|
|
2991
|
+
const structuralRootElements = rootUiElementsForKind(elements);
|
|
2992
|
+
const rootDefaultClasses = (0, utils_1.unique)(structuralRootElements.flatMap((element) => element.defaultClasses ?? element.classes)).slice(0, 80);
|
|
2993
|
+
const recipeDefaultClasses = kind === 'card' && rootDefaultClasses.length ? rootDefaultClasses : defaultClasses;
|
|
2994
|
+
const nestedDefaultClasses = kind === 'card'
|
|
2995
|
+
? (0, utils_1.unique)(elements
|
|
2996
|
+
.filter((element) => !structuralRootElements.includes(element))
|
|
2997
|
+
.flatMap((element) => element.defaultClasses ?? element.classes)
|
|
2998
|
+
.filter((className) => !(0, extractors_1.isDecorativeAccentClass)(className))).slice(0, 80)
|
|
2999
|
+
: undefined;
|
|
3000
|
+
const actionUsages = kind === 'button' ? summarizeActionUsages(elements) : undefined;
|
|
3001
|
+
const uiContract = createUiPatternContract({
|
|
3002
|
+
name: `${(0, utils_1.titleCase)(kind)} pattern`,
|
|
3003
|
+
count: elements.length,
|
|
3004
|
+
kind,
|
|
3005
|
+
tag: structuralRootElements[0]?.originalTag,
|
|
3006
|
+
layoutRole: structuralRootElements[0]?.layoutRole,
|
|
3007
|
+
label: structuralRootElements[0]?.label,
|
|
3008
|
+
line: structuralRootElements[0]?.line,
|
|
3009
|
+
path: file.relPath,
|
|
3010
|
+
sourceHash: file.sourceHash,
|
|
3011
|
+
confidence: elements.length > 1 ? 'medium' : 'low',
|
|
3012
|
+
evidence: structuralRootElements[0]?.evidence,
|
|
3013
|
+
classSource: structuralRootElements[0]?.classSource,
|
|
3014
|
+
classes,
|
|
3015
|
+
defaultClasses: recipeDefaultClasses,
|
|
3016
|
+
conditionalClasses,
|
|
3017
|
+
decorativeAccentClasses,
|
|
3018
|
+
classGroups,
|
|
3019
|
+
layoutSafety,
|
|
3020
|
+
responsiveProfile,
|
|
3021
|
+
scaleProfile,
|
|
3022
|
+
semanticProfile,
|
|
3023
|
+
roleSignature: roleSignatures[0],
|
|
3024
|
+
variants,
|
|
3025
|
+
styleContext,
|
|
3026
|
+
});
|
|
3027
|
+
uiContract.safetyRules = omitEmptyRecord({
|
|
3028
|
+
...uiContract.safetyRules,
|
|
3029
|
+
generationConstraints,
|
|
3030
|
+
});
|
|
3031
|
+
const relationships = createUiRelationships({
|
|
3032
|
+
parentContext: parentContexts,
|
|
3033
|
+
childStructureContract,
|
|
3034
|
+
compoundStructure,
|
|
3035
|
+
graphKeys: dependencies,
|
|
3036
|
+
});
|
|
737
3037
|
addEntry(entries, usedKeys, {
|
|
738
3038
|
key: ruleKey,
|
|
739
3039
|
group: 'rule',
|
|
@@ -744,8 +3044,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
744
3044
|
kind,
|
|
745
3045
|
...Object.keys(variants),
|
|
746
3046
|
...Object.values(variants).flatMap((values) => values),
|
|
747
|
-
...
|
|
3047
|
+
...recipeDefaultClasses.slice(0, 20),
|
|
748
3048
|
...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
|
|
3049
|
+
...(layoutSafety
|
|
3050
|
+
? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 16)]
|
|
3051
|
+
: []),
|
|
3052
|
+
...(responsiveProfile ? ['responsive-contract'] : []),
|
|
3053
|
+
...(scaleProfile ? ['scale-contract'] : []),
|
|
3054
|
+
...(roleSignatures.length
|
|
3055
|
+
? [
|
|
3056
|
+
'role-signature',
|
|
3057
|
+
...roleSignatures.flatMap((signature) => [
|
|
3058
|
+
signature.role,
|
|
3059
|
+
signature.roleGroup,
|
|
3060
|
+
signature.scale,
|
|
3061
|
+
signature.density,
|
|
3062
|
+
signature.surface,
|
|
3063
|
+
signature.layout,
|
|
3064
|
+
...signature.flags,
|
|
3065
|
+
]),
|
|
3066
|
+
]
|
|
3067
|
+
: []),
|
|
3068
|
+
...(semanticProfile?.isCompound
|
|
3069
|
+
? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
|
|
3070
|
+
: []),
|
|
749
3071
|
]),
|
|
750
3072
|
dependencies,
|
|
751
3073
|
usedBy: [],
|
|
@@ -759,12 +3081,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
759
3081
|
concept: kind,
|
|
760
3082
|
path: file.relPath,
|
|
761
3083
|
classes,
|
|
762
|
-
defaultClasses,
|
|
3084
|
+
defaultClasses: recipeDefaultClasses,
|
|
3085
|
+
rootDefaultClasses,
|
|
3086
|
+
...(nestedDefaultClasses?.length ? { nestedDefaultClasses } : {}),
|
|
763
3087
|
conditionalClasses,
|
|
764
3088
|
decorativeAccentClasses,
|
|
765
|
-
|
|
3089
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
3090
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
3091
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
3092
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
3093
|
+
...(roleSignatures.length ? { roleSignatures } : {}),
|
|
3094
|
+
...(parentContexts.length ? { parentContexts } : {}),
|
|
3095
|
+
...(childStructureContract ? { childStructureContract } : {}),
|
|
3096
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3097
|
+
generationConstraints,
|
|
3098
|
+
uiContract,
|
|
3099
|
+
relationships,
|
|
3100
|
+
guidance: (0, utils_1.unique)([
|
|
3101
|
+
...(0, extractors_1.createUiPatternGuidance)(kind, decorativeAccentClasses),
|
|
3102
|
+
...createUiGenerationGuidance(kind, elements),
|
|
3103
|
+
]),
|
|
766
3104
|
classGroups,
|
|
767
3105
|
variants,
|
|
3106
|
+
elementPatterns,
|
|
3107
|
+
...(actionUsages ? { actionUsages } : {}),
|
|
768
3108
|
elements: elements.slice(0, 80),
|
|
769
3109
|
sourceHash: file.sourceHash,
|
|
770
3110
|
source: sourceInfo('ui.patterns', elements.length > 1 ? 'medium' : 'low', [`${kind}:${elements.length}`], file),
|
|
@@ -773,8 +3113,12 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
773
3113
|
nodes.push(toGraphNode(ruleKey, 'rule', `${kind} pattern`, file.relPath, file, 'ui.patterns', {
|
|
774
3114
|
ui: {
|
|
775
3115
|
concept: kind,
|
|
776
|
-
classes: defaultClasses,
|
|
3116
|
+
classes: rootDefaultClasses.length ? rootDefaultClasses : defaultClasses,
|
|
777
3117
|
decorativeAccentClasses,
|
|
3118
|
+
layoutSafety,
|
|
3119
|
+
responsiveProfile,
|
|
3120
|
+
scaleProfile,
|
|
3121
|
+
semanticProfile,
|
|
778
3122
|
classGroups,
|
|
779
3123
|
variants,
|
|
780
3124
|
count: elements.length,
|
|
@@ -784,8 +3128,11 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
784
3128
|
}
|
|
785
3129
|
}
|
|
786
3130
|
}
|
|
787
|
-
function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
3131
|
+
function addUiCompositionRules(entries, usedKeys, nodes, edges, files, styleContext) {
|
|
788
3132
|
for (const file of files) {
|
|
3133
|
+
if ((0, extractors_1.isTestFile)(file.relPath)) {
|
|
3134
|
+
continue;
|
|
3135
|
+
}
|
|
789
3136
|
if (!file.uiElements.length && !file.classExpressions.length) {
|
|
790
3137
|
continue;
|
|
791
3138
|
}
|
|
@@ -819,6 +3166,46 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
819
3166
|
const classExpressionsByTarget = (0, extractors_1.groupClassExpressionsByTarget)(file.classExpressions);
|
|
820
3167
|
const layoutRoles = (0, utils_1.unique)(file.uiElements.flatMap((element) => element.layoutRole ?? []));
|
|
821
3168
|
const elementKinds = (0, utils_1.unique)(file.uiElements.map((element) => element.kind));
|
|
3169
|
+
const layoutSafety = summarizePatternLayoutSafety(file.uiElements);
|
|
3170
|
+
const responsiveProfile = summarizePatternResponsiveProfile(file.uiElements);
|
|
3171
|
+
const scaleProfile = summarizePatternScaleProfile(file.uiElements);
|
|
3172
|
+
const semanticProfile = summarizePatternSemanticProfile(file.uiElements);
|
|
3173
|
+
const parentContexts = summarizeParentContexts(file.uiElements);
|
|
3174
|
+
const childStructureContract = summarizePatternChildStructure(compositionElements, styleContext);
|
|
3175
|
+
const compoundStructure = summarizeCompoundStructure(file.uiElements, styleContext);
|
|
3176
|
+
const uiContract = createUiPatternContract({
|
|
3177
|
+
name: `${file.relPath} UI composition`,
|
|
3178
|
+
count: file.uiElements.length,
|
|
3179
|
+
kind: 'composition',
|
|
3180
|
+
tag: compositionElements[0]?.originalTag,
|
|
3181
|
+
layoutRole: layoutRoles[0],
|
|
3182
|
+
line: compositionElements[0]?.line,
|
|
3183
|
+
path: file.relPath,
|
|
3184
|
+
sourceHash: file.sourceHash,
|
|
3185
|
+
confidence: file.components.length || file.routes.length ? 'high' : 'medium',
|
|
3186
|
+
evidence: compositionElements[0]?.evidence,
|
|
3187
|
+
classSource: compositionElements[0]?.classSource,
|
|
3188
|
+
classes: baseClassReferences,
|
|
3189
|
+
defaultClasses: baseClassReferences,
|
|
3190
|
+
conditionalClasses,
|
|
3191
|
+
decorativeAccentClasses,
|
|
3192
|
+
classGroups: (0, extractors_1.mergeClassGroups)(compositionElements.map((element) => element.classGroups ?? {})),
|
|
3193
|
+
layoutSafety,
|
|
3194
|
+
responsiveProfile,
|
|
3195
|
+
scaleProfile,
|
|
3196
|
+
semanticProfile,
|
|
3197
|
+
styleContext,
|
|
3198
|
+
});
|
|
3199
|
+
uiContract.safetyRules = omitEmptyRecord({
|
|
3200
|
+
...uiContract.safetyRules,
|
|
3201
|
+
compositionGuidance: (0, extractors_1.createUiCompositionGuidance)(file),
|
|
3202
|
+
});
|
|
3203
|
+
const relationships = createUiRelationships({
|
|
3204
|
+
parentContext: parentContexts,
|
|
3205
|
+
childStructureContract,
|
|
3206
|
+
compoundStructure,
|
|
3207
|
+
graphKeys: dependencies,
|
|
3208
|
+
});
|
|
822
3209
|
addEntry(entries, usedKeys, {
|
|
823
3210
|
key: ruleKey,
|
|
824
3211
|
group: 'rule',
|
|
@@ -840,6 +3227,14 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
840
3227
|
...responsiveClasses.slice(0, 24),
|
|
841
3228
|
...surfaceClasses.slice(0, 24),
|
|
842
3229
|
...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
|
|
3230
|
+
...(layoutSafety
|
|
3231
|
+
? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
|
|
3232
|
+
: []),
|
|
3233
|
+
...(responsiveProfile ? ['responsive-contract'] : []),
|
|
3234
|
+
...(scaleProfile ? ['scale-contract'] : []),
|
|
3235
|
+
...(semanticProfile?.isCompound
|
|
3236
|
+
? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
|
|
3237
|
+
: []),
|
|
843
3238
|
...spacingClasses.slice(0, 24),
|
|
844
3239
|
...typographyClasses.slice(0, 24),
|
|
845
3240
|
]),
|
|
@@ -865,6 +3260,15 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
865
3260
|
conditionalClasses,
|
|
866
3261
|
decorativeAccentClasses,
|
|
867
3262
|
typographyClasses,
|
|
3263
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
3264
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
3265
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
3266
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
3267
|
+
...(parentContexts.length ? { parentContexts } : {}),
|
|
3268
|
+
...(childStructureContract ? { childStructureContract } : {}),
|
|
3269
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3270
|
+
uiContract,
|
|
3271
|
+
relationships,
|
|
868
3272
|
componentSlots: (0, extractors_1.buildComponentSlots)(file.uiElements),
|
|
869
3273
|
classExpressionsByTarget,
|
|
870
3274
|
elements: compositionElements,
|
|
@@ -883,6 +3287,10 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
883
3287
|
routes: file.routes,
|
|
884
3288
|
layoutRoles,
|
|
885
3289
|
elementKinds,
|
|
3290
|
+
layoutSafety,
|
|
3291
|
+
responsiveProfile,
|
|
3292
|
+
scaleProfile,
|
|
3293
|
+
semanticProfile,
|
|
886
3294
|
},
|
|
887
3295
|
}));
|
|
888
3296
|
edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_composition', 'ui.composition', file));
|
|
@@ -894,8 +3302,175 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
894
3302
|
}
|
|
895
3303
|
}
|
|
896
3304
|
}
|
|
897
|
-
function
|
|
898
|
-
const
|
|
3305
|
+
function addGlobalUiContractRules(entries, usedKeys, nodes, edges, files) {
|
|
3306
|
+
const eligibleFiles = files.filter((file) => !(0, extractors_1.isTestFile)(file.relPath));
|
|
3307
|
+
const shellFiles = eligibleFiles.filter(hasPageShellContractEvidence);
|
|
3308
|
+
const framedSurfaceFiles = eligibleFiles.filter(hasFramedSurfaceContractEvidence);
|
|
3309
|
+
if (shellFiles.length) {
|
|
3310
|
+
const primary = shellFiles[0];
|
|
3311
|
+
const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.page-shell.global');
|
|
3312
|
+
const cssRules = cssRuleSnapshots(shellFiles, shellCssRuleMatches).slice(0, 48);
|
|
3313
|
+
const classes = collectClassEvidence(shellFiles, (className) => /^(?:app-shell|bg-background|min-h-screen|flex|flex-col|text-foreground)$/.test(className));
|
|
3314
|
+
addEntry(entries, usedKeys, {
|
|
3315
|
+
key: ruleKey,
|
|
3316
|
+
group: 'rule',
|
|
3317
|
+
summary: 'Global page shell contract: body/app-shell owns the page background, route roots stay transparent.',
|
|
3318
|
+
tags: (0, utils_1.unique)([
|
|
3319
|
+
'rule',
|
|
3320
|
+
'ui',
|
|
3321
|
+
'global',
|
|
3322
|
+
'page-shell',
|
|
3323
|
+
'app-shell',
|
|
3324
|
+
'body-background',
|
|
3325
|
+
'route-root',
|
|
3326
|
+
'transparent-root',
|
|
3327
|
+
'bg-background',
|
|
3328
|
+
...classes,
|
|
3329
|
+
]),
|
|
3330
|
+
dependencies: contractDependencies(shellFiles),
|
|
3331
|
+
usedBy: [],
|
|
3332
|
+
confidence: 'high',
|
|
3333
|
+
status: 'inferred',
|
|
3334
|
+
path: primary.relPath,
|
|
3335
|
+
value: {
|
|
3336
|
+
name: 'Global page shell contract',
|
|
3337
|
+
kind: 'rule',
|
|
3338
|
+
ruleType: 'ui-page-shell',
|
|
3339
|
+
backgroundOwner: 'body/app-shell',
|
|
3340
|
+
keepRouteRootsTransparent: true,
|
|
3341
|
+
classes,
|
|
3342
|
+
requiredShellClasses: collectClassEvidence(shellFiles, (className) => /^(?:app-shell|min-h-screen|flex|flex-col)$/.test(className)),
|
|
3343
|
+
avoidOnRouteRoot: ['bg-background', 'min-h-screen bg-background'],
|
|
3344
|
+
routeRootGuidance: [
|
|
3345
|
+
'Route-level page roots inside app-shell should stay transparent; do not add bg-background or a solid page wrapper unless the source route deliberately owns an isolated full-screen surface.',
|
|
3346
|
+
'Let body/app-shell own the global gradient/glow/background and min-height behavior; page content should add spacing, containers, and sections without covering that shell.',
|
|
3347
|
+
'If a generated page needs full-height layout, prefer flex/min-height on the existing shell/layout contract instead of introducing a new bg-background root.',
|
|
3348
|
+
],
|
|
3349
|
+
cssRules,
|
|
3350
|
+
sourceHash: primary.sourceHash,
|
|
3351
|
+
source: sourceInfo('ui.global-contracts', 'high', shellFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
|
|
3352
|
+
},
|
|
3353
|
+
});
|
|
3354
|
+
nodes.push(toGraphNode(ruleKey, 'rule', 'Global page shell contract', primary.relPath, primary, 'ui.global-contracts', {
|
|
3355
|
+
ui: {
|
|
3356
|
+
ruleType: 'ui-page-shell',
|
|
3357
|
+
backgroundOwner: 'body/app-shell',
|
|
3358
|
+
keepRouteRootsTransparent: true,
|
|
3359
|
+
classes,
|
|
3360
|
+
},
|
|
3361
|
+
}));
|
|
3362
|
+
for (const file of shellFiles.slice(0, 80)) {
|
|
3363
|
+
edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3364
|
+
if (hasStyleSurface(file)) {
|
|
3365
|
+
edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
if (framedSurfaceFiles.length) {
|
|
3370
|
+
const primary = framedSurfaceFiles[0];
|
|
3371
|
+
const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.surface.section-frame-grid');
|
|
3372
|
+
const cssRules = cssRuleSnapshots(framedSurfaceFiles, framedSurfaceCssRuleMatches).slice(0, 48);
|
|
3373
|
+
const classes = collectClassEvidence(framedSurfaceFiles, (className) => /^(?:section-frame|sm:section-frame|context-surface|bg-card|bg-\[var\(--surface-soft\)\]|rounded)/.test(className));
|
|
3374
|
+
addEntry(entries, usedKeys, {
|
|
3375
|
+
key: ruleKey,
|
|
3376
|
+
group: 'rule',
|
|
3377
|
+
summary: 'Framed technical surface contract: section-frame paints a clipped grid overlay and context-surface protects child content.',
|
|
3378
|
+
tags: (0, utils_1.unique)([
|
|
3379
|
+
'rule',
|
|
3380
|
+
'ui',
|
|
3381
|
+
'surface',
|
|
3382
|
+
'framed-surface',
|
|
3383
|
+
'section-frame',
|
|
3384
|
+
'context-surface',
|
|
3385
|
+
'grid-overlay',
|
|
3386
|
+
'pseudo-element',
|
|
3387
|
+
'nested-surface',
|
|
3388
|
+
...classes,
|
|
3389
|
+
]),
|
|
3390
|
+
dependencies: contractDependencies(framedSurfaceFiles),
|
|
3391
|
+
usedBy: [],
|
|
3392
|
+
confidence: 'high',
|
|
3393
|
+
status: 'inferred',
|
|
3394
|
+
path: primary.relPath,
|
|
3395
|
+
value: {
|
|
3396
|
+
name: 'Section frame grid surface contract',
|
|
3397
|
+
kind: 'rule',
|
|
3398
|
+
ruleType: 'ui-framed-surface',
|
|
3399
|
+
surfaceClasses: ['section-frame', 'context-surface'],
|
|
3400
|
+
classes,
|
|
3401
|
+
paintModel: {
|
|
3402
|
+
sectionFrame: 'section-frame is not only border/background; it owns the clipped section-frame::before grid overlay for that exact surface.',
|
|
3403
|
+
contextSurface: 'context-surface keeps direct children above the parent overlay, preserving readable content and separate child surfaces.',
|
|
3404
|
+
},
|
|
3405
|
+
nestingGuidance: [
|
|
3406
|
+
'For parent technical panels, use section-frame with context-surface when content or child cards must sit above the parent grid.',
|
|
3407
|
+
'Nested framed child surfaces need their own section-frame/context-surface pairing when the design expects each child to have an independent clipped grid origin.',
|
|
3408
|
+
'Do not rely on one ancestor section-frame grid for child cards that visually have separate framed grids in the source design.',
|
|
3409
|
+
],
|
|
3410
|
+
usageGuidance: [
|
|
3411
|
+
'Use this contract for framed technical panels and code/context surfaces, not every ordinary repeated card.',
|
|
3412
|
+
'Preserve responsive forms such as sm:section-frame when the source intentionally keeps mobile surfaces transparent or unframed.',
|
|
3413
|
+
],
|
|
3414
|
+
avoid: [
|
|
3415
|
+
'Do not add decorative glow/shadow classes to repeated cards just because they are framed.',
|
|
3416
|
+
'Do not flatten parent and child frame classes onto one root; preserve the surface hierarchy.',
|
|
3417
|
+
],
|
|
3418
|
+
cssRules,
|
|
3419
|
+
sourceHash: primary.sourceHash,
|
|
3420
|
+
source: sourceInfo('ui.global-contracts', 'high', framedSurfaceFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
|
|
3421
|
+
},
|
|
3422
|
+
});
|
|
3423
|
+
nodes.push(toGraphNode(ruleKey, 'rule', 'Section frame grid surface contract', primary.relPath, primary, 'ui.global-contracts', {
|
|
3424
|
+
ui: {
|
|
3425
|
+
ruleType: 'ui-framed-surface',
|
|
3426
|
+
surfaceClasses: ['section-frame', 'context-surface'],
|
|
3427
|
+
classes,
|
|
3428
|
+
},
|
|
3429
|
+
}));
|
|
3430
|
+
for (const file of framedSurfaceFiles.slice(0, 80)) {
|
|
3431
|
+
edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3432
|
+
if (hasStyleSurface(file)) {
|
|
3433
|
+
edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
function hasPageShellContractEvidence(file) {
|
|
3439
|
+
return Boolean(file.classReferences.includes('app-shell') ||
|
|
3440
|
+
file.cssClasses.includes('app-shell') ||
|
|
3441
|
+
file.cssRules.some(shellCssRuleMatches) ||
|
|
3442
|
+
(file.relPath.endsWith('layout.tsx') && file.classReferences.includes('bg-background')));
|
|
3443
|
+
}
|
|
3444
|
+
function hasFramedSurfaceContractEvidence(file) {
|
|
3445
|
+
return Boolean(file.classReferences.some((className) => ['section-frame', 'sm:section-frame', 'context-surface'].includes(className)) ||
|
|
3446
|
+
file.cssClasses.some((className) => ['section-frame', 'context-surface'].includes(className)) ||
|
|
3447
|
+
file.cssRules.some(framedSurfaceCssRuleMatches));
|
|
3448
|
+
}
|
|
3449
|
+
function shellCssRuleMatches(rule) {
|
|
3450
|
+
return /(^|[,\s])(?:html|body|:root)(?:$|[,\s:{.#])|\.app-shell\b|\.bg-background\b/.test(rule.selector);
|
|
3451
|
+
}
|
|
3452
|
+
function framedSurfaceCssRuleMatches(rule) {
|
|
3453
|
+
return /\.section-frame\b|\.context-surface\b/.test(rule.selector);
|
|
3454
|
+
}
|
|
3455
|
+
function contractDependencies(files) {
|
|
3456
|
+
return (0, utils_1.unique)(files.flatMap((file) => [file.key, ...(hasStyleSurface(file) ? [styleKeyForFile(file)] : [])]));
|
|
3457
|
+
}
|
|
3458
|
+
function styleKeyForFile(file) {
|
|
3459
|
+
return (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
|
|
3460
|
+
}
|
|
3461
|
+
function collectClassEvidence(files, predicate) {
|
|
3462
|
+
return (0, utils_1.unique)(files.flatMap((file) => [...file.classReferences, ...file.cssClasses].filter(predicate))).slice(0, 120);
|
|
3463
|
+
}
|
|
3464
|
+
function cssRuleSnapshots(files, predicate) {
|
|
3465
|
+
return (0, utils_1.uniqueBy)(files.flatMap((file) => file.cssRules.filter(predicate).map((rule) => ({
|
|
3466
|
+
path: file.relPath,
|
|
3467
|
+
selector: rule.selector,
|
|
3468
|
+
line: rule.line,
|
|
3469
|
+
declarations: rule.declarations.slice(0, 24),
|
|
3470
|
+
}))), (rule) => `${rule.path}:${rule.selector}:${rule.line}`);
|
|
3471
|
+
}
|
|
3472
|
+
function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files, styleContext) {
|
|
3473
|
+
const clusters = buildConnectedPatternClusters(files, styleContext);
|
|
899
3474
|
for (const cluster of clusters) {
|
|
900
3475
|
addEntry(entries, usedKeys, {
|
|
901
3476
|
key: cluster.key,
|
|
@@ -967,9 +3542,9 @@ function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems,
|
|
|
967
3542
|
}
|
|
968
3543
|
}
|
|
969
3544
|
}
|
|
970
|
-
function buildConnectedPatternClusters(files) {
|
|
3545
|
+
function buildConnectedPatternClusters(files, styleContext) {
|
|
971
3546
|
const instances = [
|
|
972
|
-
...files.flatMap(createFrontendPatternInstances),
|
|
3547
|
+
...files.flatMap((file) => createFrontendPatternInstances(file, styleContext)),
|
|
973
3548
|
...createBackendPatternInstances(files),
|
|
974
3549
|
...createDataPatternInstances(files),
|
|
975
3550
|
];
|
|
@@ -990,7 +3565,62 @@ function buildConnectedPatternClusters(files) {
|
|
|
990
3565
|
const classGroups = (0, extractors_1.mergeClassGroups)(group
|
|
991
3566
|
.map((instance) => instance.root?.classGroups)
|
|
992
3567
|
.filter((value) => Boolean(value)));
|
|
3568
|
+
const layoutSafety = summarizeConnectedLayoutSafety(group);
|
|
3569
|
+
const responsiveProfile = summarizeConnectedResponsiveProfile(group);
|
|
3570
|
+
const scaleProfile = summarizeConnectedScaleProfile(group);
|
|
3571
|
+
const semanticProfile = summarizeConnectedSemanticProfile(group);
|
|
3572
|
+
const roleSignatures = summarizeConnectedRoleSignatures(group);
|
|
3573
|
+
const roleSignature = roleSignatures[0];
|
|
3574
|
+
const compoundStructure = summarizeConnectedCompoundStructure(group);
|
|
3575
|
+
const parentContexts = summarizeConnectedParentContexts(group);
|
|
993
3576
|
const childStructure = summarizeConnectedChildren(group);
|
|
3577
|
+
const graphKeys = (0, utils_1.unique)(group.flatMap((instance) => instance.graphKeys)).slice(0, 80);
|
|
3578
|
+
const structureContract = {
|
|
3579
|
+
preserveRootAndChildrenTogether: true,
|
|
3580
|
+
preserveParentLayoutContext: Boolean(parentContexts.length),
|
|
3581
|
+
preserveDirectChildRelationships: Boolean(childStructure.length),
|
|
3582
|
+
preserveCompoundChildRoles: Boolean(compoundStructure),
|
|
3583
|
+
preserveResponsiveBreakpoints: Boolean(responsiveProfile),
|
|
3584
|
+
preserveLayoutSafety: Boolean(layoutSafety),
|
|
3585
|
+
preserveScaleBounds: Boolean(scaleProfile),
|
|
3586
|
+
preserveRoleSignature: Boolean(roleSignature),
|
|
3587
|
+
avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
|
|
3588
|
+
};
|
|
3589
|
+
const uiContract = createUiPatternContract({
|
|
3590
|
+
name: `${(0, utils_1.titleCase)(first.concept)} connected pattern`,
|
|
3591
|
+
count: group.length,
|
|
3592
|
+
kind: first.concept,
|
|
3593
|
+
tag: first.root?.tag,
|
|
3594
|
+
layoutRole: first.root?.layoutRole,
|
|
3595
|
+
path: first.file.relPath,
|
|
3596
|
+
sourceHash: first.file.sourceHash,
|
|
3597
|
+
confidence,
|
|
3598
|
+
evidence: first.evidence.label,
|
|
3599
|
+
classes: defaultClasses,
|
|
3600
|
+
defaultClasses,
|
|
3601
|
+
conditionalClasses,
|
|
3602
|
+
decorativeAccentClasses,
|
|
3603
|
+
classGroups,
|
|
3604
|
+
layoutSafety,
|
|
3605
|
+
responsiveProfile,
|
|
3606
|
+
scaleProfile,
|
|
3607
|
+
semanticProfile,
|
|
3608
|
+
roleSignature,
|
|
3609
|
+
styleContext,
|
|
3610
|
+
});
|
|
3611
|
+
uiContract.safetyRules = omitEmptyRecord({
|
|
3612
|
+
...uiContract.safetyRules,
|
|
3613
|
+
structureContract,
|
|
3614
|
+
});
|
|
3615
|
+
const relationships = createUiRelationships({
|
|
3616
|
+
parentContext: parentContexts,
|
|
3617
|
+
childStructureContract: {
|
|
3618
|
+
children: childStructure,
|
|
3619
|
+
preserveDirectChildRelationships: Boolean(childStructure.length),
|
|
3620
|
+
},
|
|
3621
|
+
compoundStructure,
|
|
3622
|
+
graphKeys,
|
|
3623
|
+
});
|
|
994
3624
|
const key = (0, utils_1.toRegistryKey)('cluster', `pattern.${first.domain}.${first.concept}.${connectedPatternSlug(first)}.${(0, utils_1.shortHash)(first.signature)}`);
|
|
995
3625
|
const fileCount = new Set(group.map((instance) => instance.file.relPath)).size;
|
|
996
3626
|
clusters.push({
|
|
@@ -1015,6 +3645,28 @@ function buildConnectedPatternClusters(files) {
|
|
|
1015
3645
|
...group.flatMap((instance) => instance.tags).slice(0, 80),
|
|
1016
3646
|
...defaultClasses.slice(0, 24),
|
|
1017
3647
|
...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
|
|
3648
|
+
...(layoutSafety
|
|
3649
|
+
? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
|
|
3650
|
+
: []),
|
|
3651
|
+
...(responsiveProfile ? ['responsive-contract'] : []),
|
|
3652
|
+
...(scaleProfile ? ['scale-contract'] : []),
|
|
3653
|
+
...(semanticProfile?.isCompound
|
|
3654
|
+
? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
|
|
3655
|
+
: []),
|
|
3656
|
+
...(roleSignatures.length
|
|
3657
|
+
? [
|
|
3658
|
+
'role-signature',
|
|
3659
|
+
...roleSignatures.flatMap((signature) => [
|
|
3660
|
+
signature.role,
|
|
3661
|
+
signature.roleGroup,
|
|
3662
|
+
signature.scale,
|
|
3663
|
+
signature.density,
|
|
3664
|
+
signature.surface,
|
|
3665
|
+
signature.layout,
|
|
3666
|
+
...signature.flags,
|
|
3667
|
+
]),
|
|
3668
|
+
]
|
|
3669
|
+
: []),
|
|
1018
3670
|
]),
|
|
1019
3671
|
dependencies,
|
|
1020
3672
|
value: {
|
|
@@ -1034,9 +3686,19 @@ function buildConnectedPatternClusters(files) {
|
|
|
1034
3686
|
conditionalClasses,
|
|
1035
3687
|
decorativeAccentClasses,
|
|
1036
3688
|
classGroups,
|
|
3689
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
3690
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
3691
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
3692
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
3693
|
+
...(roleSignatures.length ? { roleSignatures } : {}),
|
|
3694
|
+
...(parentContexts.length ? { parentContexts } : {}),
|
|
1037
3695
|
childStructure,
|
|
3696
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3697
|
+
structureContract,
|
|
3698
|
+
uiContract,
|
|
3699
|
+
relationships,
|
|
1038
3700
|
examples,
|
|
1039
|
-
graphKeys
|
|
3701
|
+
graphKeys,
|
|
1040
3702
|
guidance: createConnectedPatternGuidance(first, group, decorativeAccentClasses),
|
|
1041
3703
|
variants: connectedPatternVariants(first.domain, group),
|
|
1042
3704
|
children: connectedPatternChildren(first.domain, first.concept, group, dependencies),
|
|
@@ -1080,8 +3742,11 @@ function buildConnectedPatternClusters(files) {
|
|
|
1080
3742
|
}
|
|
1081
3743
|
return clusters.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
|
|
1082
3744
|
}
|
|
1083
|
-
function createFrontendPatternInstances(file) {
|
|
3745
|
+
function createFrontendPatternInstances(file, styleContext) {
|
|
1084
3746
|
const instances = [];
|
|
3747
|
+
if ((0, extractors_1.isTestFile)(file.relPath)) {
|
|
3748
|
+
return instances;
|
|
3749
|
+
}
|
|
1085
3750
|
for (const element of file.uiElements) {
|
|
1086
3751
|
if (!isConnectedUiPatternRoot(element)) {
|
|
1087
3752
|
continue;
|
|
@@ -1093,7 +3758,14 @@ function createFrontendPatternInstances(file) {
|
|
|
1093
3758
|
...(0, extractors_1.filterClasses)(element.classes, extractors_1.isDecorativeAccentClass),
|
|
1094
3759
|
]).slice(0, 60);
|
|
1095
3760
|
const classGroups = (0, extractors_1.groupTailwindClasses)(defaultClasses.length ? defaultClasses : element.classes);
|
|
1096
|
-
const children = normalizeConnectedChildren(element.childSummary ?? []);
|
|
3761
|
+
const children = normalizeConnectedChildren(element.childSummary ?? [], styleContext);
|
|
3762
|
+
const parentContext = summarizeParentContexts([element])[0];
|
|
3763
|
+
const childStructureContract = element.childSummary?.length
|
|
3764
|
+
? summarizeElementChildContract(element.childSummary, styleContext)
|
|
3765
|
+
: undefined;
|
|
3766
|
+
const compoundStructure = element.semanticProfile?.isCompound
|
|
3767
|
+
? summarizeCompoundStructure([element], styleContext)
|
|
3768
|
+
: undefined;
|
|
1097
3769
|
const component = file.components[0];
|
|
1098
3770
|
const route = file.routes[0];
|
|
1099
3771
|
const graphKeys = (0, utils_1.unique)([
|
|
@@ -1110,10 +3782,44 @@ function createFrontendPatternInstances(file) {
|
|
|
1110
3782
|
'frontend',
|
|
1111
3783
|
element.kind,
|
|
1112
3784
|
normalizeLayoutRole(element.layoutRole),
|
|
3785
|
+
roleSignatureKey(element.roleSignature),
|
|
1113
3786
|
normalizeTagForSignature(element),
|
|
1114
3787
|
classSignature(defaultClasses),
|
|
1115
3788
|
children.map((child) => child.signature).join('|'),
|
|
1116
3789
|
].join(';');
|
|
3790
|
+
const uiContract = createUiContract({
|
|
3791
|
+
kind: element.kind,
|
|
3792
|
+
tag: element.originalTag,
|
|
3793
|
+
layoutRole: element.layoutRole,
|
|
3794
|
+
label: element.label,
|
|
3795
|
+
line: element.line,
|
|
3796
|
+
path: file.relPath,
|
|
3797
|
+
sourceHash: file.sourceHash,
|
|
3798
|
+
confidence: 'medium',
|
|
3799
|
+
evidence: element.evidence,
|
|
3800
|
+
classSource: element.classSource,
|
|
3801
|
+
classes: element.classes,
|
|
3802
|
+
defaultClasses,
|
|
3803
|
+
conditionalClasses,
|
|
3804
|
+
decorativeAccentClasses,
|
|
3805
|
+
classGroups,
|
|
3806
|
+
layoutSafety: element.layoutSafety,
|
|
3807
|
+
responsiveProfile: element.responsiveProfile,
|
|
3808
|
+
scaleProfile: element.scaleProfile,
|
|
3809
|
+
semanticProfile: element.semanticProfile,
|
|
3810
|
+
roleSignature: element.roleSignature,
|
|
3811
|
+
variants: element.variants,
|
|
3812
|
+
props: element.props,
|
|
3813
|
+
styleHelper: element.styleHelper,
|
|
3814
|
+
styleHelperVariants: element.styleHelperVariants,
|
|
3815
|
+
styleContext,
|
|
3816
|
+
});
|
|
3817
|
+
const relationships = createUiRelationships({
|
|
3818
|
+
parentContext,
|
|
3819
|
+
childStructureContract,
|
|
3820
|
+
compoundStructure,
|
|
3821
|
+
graphKeys,
|
|
3822
|
+
});
|
|
1117
3823
|
instances.push({
|
|
1118
3824
|
domain: 'frontend',
|
|
1119
3825
|
concept: element.kind,
|
|
@@ -1129,6 +3835,12 @@ function createFrontendPatternInstances(file) {
|
|
|
1129
3835
|
conditionalClasses,
|
|
1130
3836
|
decorativeAccentClasses,
|
|
1131
3837
|
classGroups,
|
|
3838
|
+
...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
|
|
3839
|
+
...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
|
|
3840
|
+
...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
|
|
3841
|
+
...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
|
|
3842
|
+
...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
|
|
3843
|
+
uiContract,
|
|
1132
3844
|
},
|
|
1133
3845
|
children: children.map(withoutConnectedChildSignature),
|
|
1134
3846
|
graphKeys,
|
|
@@ -1142,6 +3854,25 @@ function createFrontendPatternInstances(file) {
|
|
|
1142
3854
|
...file.components,
|
|
1143
3855
|
...file.routes,
|
|
1144
3856
|
...Object.keys(classGroups),
|
|
3857
|
+
...(element.layoutSafety ? ['layout-safety'] : []),
|
|
3858
|
+
...(element.responsiveProfile ? ['responsive-contract'] : []),
|
|
3859
|
+
...(element.scaleProfile ? ['scale-contract'] : []),
|
|
3860
|
+
...(element.roleSignature
|
|
3861
|
+
? [
|
|
3862
|
+
'role-signature',
|
|
3863
|
+
element.roleSignature.role,
|
|
3864
|
+
element.roleSignature.roleGroup,
|
|
3865
|
+
element.roleSignature.scale,
|
|
3866
|
+
element.roleSignature.density,
|
|
3867
|
+
element.roleSignature.surface,
|
|
3868
|
+
element.roleSignature.layout,
|
|
3869
|
+
...element.roleSignature.flags,
|
|
3870
|
+
...element.roleSignature.exactClassFacts.slice(0, 24),
|
|
3871
|
+
]
|
|
3872
|
+
: []),
|
|
3873
|
+
...(element.semanticProfile?.isCompound
|
|
3874
|
+
? ['compound-ui', 'compound-badge', ...element.semanticProfile.roles]
|
|
3875
|
+
: []),
|
|
1145
3876
|
]),
|
|
1146
3877
|
evidence: {
|
|
1147
3878
|
path: file.relPath,
|
|
@@ -1160,8 +3891,18 @@ function createFrontendPatternInstances(file) {
|
|
|
1160
3891
|
conditionalClasses,
|
|
1161
3892
|
decorativeAccentClasses,
|
|
1162
3893
|
classGroups,
|
|
3894
|
+
...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
|
|
3895
|
+
...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
|
|
3896
|
+
...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
|
|
3897
|
+
...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
|
|
3898
|
+
...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
|
|
3899
|
+
uiContract,
|
|
1163
3900
|
},
|
|
1164
3901
|
children: children.map(withoutConnectedChildSignature),
|
|
3902
|
+
...(parentContext ? { parentContext } : {}),
|
|
3903
|
+
...(childStructureContract ? { childStructureContract } : {}),
|
|
3904
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3905
|
+
relationships,
|
|
1165
3906
|
},
|
|
1166
3907
|
baseWeight: frontendPatternWeight(file, element, children.length, defaultClasses),
|
|
1167
3908
|
});
|
|
@@ -1185,6 +3926,7 @@ function createBackendPatternInstances(files) {
|
|
|
1185
3926
|
...file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
|
|
1186
3927
|
...file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
|
|
1187
3928
|
...file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
|
|
3929
|
+
...file.databaseAccesses.flatMap(databaseAccessDependencies),
|
|
1188
3930
|
...file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
|
|
1189
3931
|
]);
|
|
1190
3932
|
const signature = [
|
|
@@ -1195,7 +3937,7 @@ function createBackendPatternInstances(files) {
|
|
|
1195
3937
|
`methods:${methodShapes.join(',') || 'none'}`,
|
|
1196
3938
|
`controller:${Boolean(file.controllers.length)}`,
|
|
1197
3939
|
`service:${Boolean(file.services.length)}`,
|
|
1198
|
-
`model:${Boolean(file.models.length)}`,
|
|
3940
|
+
`model:${Boolean(file.models.length || file.databaseAccesses.length)}`,
|
|
1199
3941
|
`auth:${Boolean(file.authHints.length || file.middleware.length)}`,
|
|
1200
3942
|
`events:${Boolean(file.events.length || file.jobs.length)}`,
|
|
1201
3943
|
`calls:${Boolean(file.apiCalls.length)}`,
|
|
@@ -1234,6 +3976,7 @@ function createBackendPatternInstances(files) {
|
|
|
1234
3976
|
modules: file.modules,
|
|
1235
3977
|
routes: file.routes,
|
|
1236
3978
|
models: file.models,
|
|
3979
|
+
databaseAccesses: file.databaseAccesses,
|
|
1237
3980
|
apiCalls: file.apiCalls,
|
|
1238
3981
|
envVars: file.envVars,
|
|
1239
3982
|
authHints: file.authHints,
|
|
@@ -1258,12 +4001,19 @@ function createDataPatternInstances(files) {
|
|
|
1258
4001
|
const instances = [];
|
|
1259
4002
|
for (const file of files) {
|
|
1260
4003
|
for (const model of file.models) {
|
|
1261
|
-
const
|
|
4004
|
+
const databaseModel = file.databaseModels.find((candidate) => candidate.name === model);
|
|
4005
|
+
const fields = databaseModel
|
|
4006
|
+
? databaseFieldShapeInputs(databaseModel)
|
|
4007
|
+
: (file.modelFields[model] ?? []);
|
|
1262
4008
|
const fieldShape = summarizeModelFieldShape(fields);
|
|
4009
|
+
const accessGraphKeys = file.databaseAccesses
|
|
4010
|
+
.filter((access) => access.model === model || access.table === databaseModel?.tableName)
|
|
4011
|
+
.map((access) => (0, utils_1.toRegistryKey)('database', `access.${file.relPath.replace(/\.[^.]+$/, '')}.${access.line}.${access.operation}.${access.model ?? access.table ?? access.receiver ?? 'unknown'}`));
|
|
1263
4012
|
const signature = [
|
|
1264
4013
|
'data',
|
|
1265
|
-
'model',
|
|
1266
|
-
detectFrameworkForFile(file) ?? file.language,
|
|
4014
|
+
databaseModel?.kind ?? 'model',
|
|
4015
|
+
databaseModel?.provider ?? detectFrameworkForFile(file) ?? file.language,
|
|
4016
|
+
databaseModel?.tableName ?? 'no-table',
|
|
1267
4017
|
fieldShape.signature,
|
|
1268
4018
|
].join(';');
|
|
1269
4019
|
instances.push({
|
|
@@ -1272,8 +4022,24 @@ function createDataPatternInstances(files) {
|
|
|
1272
4022
|
signature,
|
|
1273
4023
|
summary: `${model} data model shape in ${file.relPath}.`,
|
|
1274
4024
|
file,
|
|
1275
|
-
graphKeys: (0, utils_1.unique)([
|
|
1276
|
-
|
|
4025
|
+
graphKeys: (0, utils_1.unique)([
|
|
4026
|
+
file.key,
|
|
4027
|
+
(0, utils_1.toRegistryKey)('model', model),
|
|
4028
|
+
...(hasDatabaseSurface(file)
|
|
4029
|
+
? [(0, utils_1.toRegistryKey)('database', file.relPath.replace(/\.[^.]+$/, ''))]
|
|
4030
|
+
: []),
|
|
4031
|
+
...accessGraphKeys,
|
|
4032
|
+
]),
|
|
4033
|
+
tags: (0, utils_1.unique)([
|
|
4034
|
+
'data',
|
|
4035
|
+
'database',
|
|
4036
|
+
databaseModel?.kind ?? 'model',
|
|
4037
|
+
'schema',
|
|
4038
|
+
model,
|
|
4039
|
+
databaseModel?.tableName ?? '',
|
|
4040
|
+
databaseModel?.provider ?? file.language,
|
|
4041
|
+
...fieldShape.tags,
|
|
4042
|
+
]),
|
|
1277
4043
|
evidence: {
|
|
1278
4044
|
path: file.relPath,
|
|
1279
4045
|
key: (0, utils_1.toRegistryKey)('model', model),
|
|
@@ -1281,18 +4047,35 @@ function createDataPatternInstances(files) {
|
|
|
1281
4047
|
},
|
|
1282
4048
|
value: {
|
|
1283
4049
|
model,
|
|
1284
|
-
fields,
|
|
4050
|
+
fields: databaseModel?.fieldNames ?? fields,
|
|
4051
|
+
fieldDetails: databaseModel?.fields ?? [],
|
|
1285
4052
|
fieldShape,
|
|
1286
|
-
|
|
4053
|
+
tableName: databaseModel?.tableName,
|
|
4054
|
+
provider: databaseModel?.provider,
|
|
4055
|
+
primaryKey: databaseModel?.primaryKey ?? [],
|
|
4056
|
+
uniqueFields: databaseModel?.uniqueFields ?? [],
|
|
4057
|
+
indexes: databaseModel?.indexes ?? [],
|
|
4058
|
+
relations: databaseModel?.relations ?? [],
|
|
4059
|
+
enumRefs: databaseModel?.enumRefs ?? [],
|
|
4060
|
+
accesses: file.databaseAccesses.filter((access) => access.model === model || access.table === databaseModel?.tableName),
|
|
4061
|
+
framework: databaseModel?.framework ?? detectFrameworkForFile(file),
|
|
1287
4062
|
},
|
|
1288
|
-
baseWeight: 3 +
|
|
4063
|
+
baseWeight: 3 +
|
|
4064
|
+
Math.min(fields.length / 2, 8) +
|
|
4065
|
+
Math.min((databaseModel?.relations.length ?? 0) * 0.75, 4) +
|
|
4066
|
+
Math.min(accessGraphKeys.length * 0.5, 4),
|
|
1289
4067
|
});
|
|
1290
4068
|
}
|
|
1291
4069
|
}
|
|
1292
4070
|
return instances;
|
|
1293
4071
|
}
|
|
1294
4072
|
function isConnectedUiPatternRoot(element) {
|
|
1295
|
-
if (element.kind === 'unknown' ||
|
|
4073
|
+
if (element.kind === 'unknown' ||
|
|
4074
|
+
element.kind === 'link' ||
|
|
4075
|
+
element.kind === 'text' ||
|
|
4076
|
+
element.kind === 'icon' ||
|
|
4077
|
+
element.kind === 'image' ||
|
|
4078
|
+
element.kind === 'divider') {
|
|
1296
4079
|
return false;
|
|
1297
4080
|
}
|
|
1298
4081
|
if (['button', 'input', 'badge'].includes(element.kind)) {
|
|
@@ -1300,9 +4083,12 @@ function isConnectedUiPatternRoot(element) {
|
|
|
1300
4083
|
}
|
|
1301
4084
|
return (0, extractors_1.isCompositionElement)(element) || Boolean(element.childSummary?.length);
|
|
1302
4085
|
}
|
|
1303
|
-
function normalizeConnectedChildren(children) {
|
|
4086
|
+
function normalizeConnectedChildren(children, styleContext) {
|
|
1304
4087
|
return children
|
|
1305
|
-
.filter((child) => child.kind !== 'unknown' ||
|
|
4088
|
+
.filter((child) => child.kind !== 'unknown' ||
|
|
4089
|
+
child.classes.length ||
|
|
4090
|
+
child.label ||
|
|
4091
|
+
Boolean(child.semanticRole))
|
|
1306
4092
|
.slice(0, 10)
|
|
1307
4093
|
.map((child) => {
|
|
1308
4094
|
const defaultClasses = (0, extractors_1.filterClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes, (className) => !(0, extractors_1.isDecorativeAccentClass)(className)).slice(0, 48);
|
|
@@ -1311,6 +4097,8 @@ function normalizeConnectedChildren(children) {
|
|
|
1311
4097
|
const signature = [
|
|
1312
4098
|
normalizedKind,
|
|
1313
4099
|
normalizeLayoutRole(child.layoutRole),
|
|
4100
|
+
child.semanticRole ?? 'unspecified-semantic-role',
|
|
4101
|
+
roleSignatureKey(child.roleSignature),
|
|
1314
4102
|
normalizeChildTagForSignature(child),
|
|
1315
4103
|
classSignature(defaultClasses),
|
|
1316
4104
|
].join(':');
|
|
@@ -1320,8 +4108,17 @@ function normalizeConnectedChildren(children) {
|
|
|
1320
4108
|
sourceKind: child.kind,
|
|
1321
4109
|
tag: child.originalTag,
|
|
1322
4110
|
layoutRole: child.layoutRole,
|
|
4111
|
+
semanticRole: child.semanticRole,
|
|
4112
|
+
uiContract: createUiContractForChild(child, styleContext),
|
|
1323
4113
|
defaultClasses,
|
|
1324
4114
|
conditionalClasses: child.conditionalClasses?.slice(0, 32) ?? [],
|
|
4115
|
+
layoutSafety: child.layoutSafety,
|
|
4116
|
+
responsiveProfile: child.responsiveProfile,
|
|
4117
|
+
scaleProfile: child.scaleProfile,
|
|
4118
|
+
roleSignature: child.roleSignature,
|
|
4119
|
+
variants: child.variants ?? {},
|
|
4120
|
+
styleHelper: child.styleHelper,
|
|
4121
|
+
styleHelperVariants: child.styleHelperVariants ?? {},
|
|
1325
4122
|
classGroups,
|
|
1326
4123
|
labelHint: child.label ? labelSignature(child.label) : undefined,
|
|
1327
4124
|
};
|
|
@@ -1331,25 +4128,125 @@ function withoutConnectedChildSignature(child) {
|
|
|
1331
4128
|
return Object.fromEntries(Object.entries(child).filter(([key]) => key !== 'signature'));
|
|
1332
4129
|
}
|
|
1333
4130
|
function connectedChildKind(child) {
|
|
4131
|
+
if (child.semanticRole === 'icon' || child.semanticRole === 'component-icon') {
|
|
4132
|
+
return 'icon';
|
|
4133
|
+
}
|
|
4134
|
+
if (child.semanticRole === 'image') {
|
|
4135
|
+
return 'image';
|
|
4136
|
+
}
|
|
4137
|
+
if (child.semanticRole === 'divider') {
|
|
4138
|
+
return 'divider';
|
|
4139
|
+
}
|
|
1334
4140
|
if (child.layoutRole === 'eyebrow-or-badge') {
|
|
1335
4141
|
return 'badge';
|
|
1336
4142
|
}
|
|
1337
4143
|
return child.kind;
|
|
1338
4144
|
}
|
|
4145
|
+
function isRecordValue(value) {
|
|
4146
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
4147
|
+
}
|
|
4148
|
+
function isRoleSignatureRecord(value) {
|
|
4149
|
+
return (isRecordValue(value) &&
|
|
4150
|
+
typeof value.role === 'string' &&
|
|
4151
|
+
typeof value.roleGroup === 'string' &&
|
|
4152
|
+
typeof value.scale === 'string' &&
|
|
4153
|
+
typeof value.density === 'string' &&
|
|
4154
|
+
typeof value.surface === 'string' &&
|
|
4155
|
+
typeof value.layout === 'string' &&
|
|
4156
|
+
Array.isArray(value.flags) &&
|
|
4157
|
+
Array.isArray(value.exactClassFacts));
|
|
4158
|
+
}
|
|
4159
|
+
function summarizeConnectedLayoutSafety(instances) {
|
|
4160
|
+
return mergeLayoutSafetyProfiles([
|
|
4161
|
+
...instances.map((instance) => ({ layoutSafety: instance.root?.layoutSafety })),
|
|
4162
|
+
...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
|
|
4163
|
+
layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
|
|
4164
|
+
}))),
|
|
4165
|
+
]);
|
|
4166
|
+
}
|
|
4167
|
+
function summarizeConnectedResponsiveProfile(instances) {
|
|
4168
|
+
return mergeResponsiveProfiles([
|
|
4169
|
+
...instances.map((instance) => ({ responsiveProfile: instance.root?.responsiveProfile })),
|
|
4170
|
+
...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
|
|
4171
|
+
responsiveProfile: isResponsiveProfile(child.responsiveProfile)
|
|
4172
|
+
? child.responsiveProfile
|
|
4173
|
+
: undefined,
|
|
4174
|
+
}))),
|
|
4175
|
+
]);
|
|
4176
|
+
}
|
|
4177
|
+
function summarizeConnectedScaleProfile(instances) {
|
|
4178
|
+
return mergeScaleProfiles([
|
|
4179
|
+
...instances.map((instance) => ({ scaleProfile: instance.root?.scaleProfile })),
|
|
4180
|
+
...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
|
|
4181
|
+
scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
|
|
4182
|
+
}))),
|
|
4183
|
+
]);
|
|
4184
|
+
}
|
|
4185
|
+
function summarizeConnectedSemanticProfile(instances) {
|
|
4186
|
+
return mergeSemanticProfiles(instances.map((instance) => ({ semanticProfile: instance.root?.semanticProfile })));
|
|
4187
|
+
}
|
|
4188
|
+
function summarizeConnectedRoleSignatures(instances) {
|
|
4189
|
+
return (0, utils_1.uniqueBy)(instances
|
|
4190
|
+
.map((instance) => instance.root?.roleSignature)
|
|
4191
|
+
.filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
|
|
4192
|
+
}
|
|
4193
|
+
function summarizeConnectedCompoundStructure(instances) {
|
|
4194
|
+
const compoundInstances = instances.filter((instance) => instance.root?.semanticProfile?.isCompound);
|
|
4195
|
+
if (!compoundInstances.length) {
|
|
4196
|
+
return undefined;
|
|
4197
|
+
}
|
|
4198
|
+
const childCounts = compoundInstances.map((instance) => instance.children?.length ?? 0);
|
|
4199
|
+
const childOrderExamples = (0, utils_1.uniqueBy)(compoundInstances
|
|
4200
|
+
.map((instance) => instance.value.childStructureContract)
|
|
4201
|
+
.filter((value) => isRecordValue(value)), (value) => JSON.stringify(value)).slice(0, 8);
|
|
4202
|
+
const semanticProfile = summarizeConnectedSemanticProfile(compoundInstances);
|
|
4203
|
+
return {
|
|
4204
|
+
preserveCompoundChildren: true,
|
|
4205
|
+
preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
|
|
4206
|
+
preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
|
|
4207
|
+
directChildCountRange: {
|
|
4208
|
+
min: Math.min(...childCounts),
|
|
4209
|
+
max: Math.max(...childCounts),
|
|
4210
|
+
},
|
|
4211
|
+
semanticRoles: semanticProfile?.roles ?? [],
|
|
4212
|
+
childOrderExamples,
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4215
|
+
function summarizeConnectedParentContexts(instances) {
|
|
4216
|
+
return (0, utils_1.uniqueBy)(instances
|
|
4217
|
+
.map((instance) => instance.value.parentContext)
|
|
4218
|
+
.filter((value) => isRecordValue(value)), (context) => JSON.stringify(context)).slice(0, 12);
|
|
4219
|
+
}
|
|
1339
4220
|
function summarizeConnectedChildren(instances) {
|
|
1340
4221
|
const childValues = instances.flatMap((instance) => instance.children ?? []);
|
|
1341
4222
|
const grouped = (0, utils_1.groupBy)(childValues, (child) => [
|
|
1342
4223
|
String(child.kind ?? ''),
|
|
1343
4224
|
String(child.layoutRole ?? ''),
|
|
4225
|
+
String(child.semanticRole ?? ''),
|
|
4226
|
+
roleSignatureKey(isRoleSignatureRecord(child.roleSignature) ? child.roleSignature : undefined),
|
|
1344
4227
|
JSON.stringify(child.classGroups ?? {}),
|
|
1345
4228
|
].join(':'));
|
|
1346
4229
|
return Array.from(grouped.values())
|
|
1347
4230
|
.map((children) => {
|
|
1348
4231
|
const first = children[0] ?? {};
|
|
4232
|
+
const layoutSafety = mergeLayoutSafetyProfiles(children.map((child) => ({
|
|
4233
|
+
layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
|
|
4234
|
+
})));
|
|
4235
|
+
const responsiveProfile = mergeResponsiveProfiles(children.map((child) => ({
|
|
4236
|
+
responsiveProfile: isResponsiveProfile(child.responsiveProfile)
|
|
4237
|
+
? child.responsiveProfile
|
|
4238
|
+
: undefined,
|
|
4239
|
+
})));
|
|
4240
|
+
const scaleProfile = mergeScaleProfiles(children.map((child) => ({
|
|
4241
|
+
scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
|
|
4242
|
+
})));
|
|
1349
4243
|
return {
|
|
1350
4244
|
kind: first.kind,
|
|
1351
4245
|
tag: first.tag,
|
|
1352
4246
|
layoutRole: first.layoutRole,
|
|
4247
|
+
semanticRole: first.semanticRole,
|
|
4248
|
+
roleSignature: first.roleSignature,
|
|
4249
|
+
uiContract: first.uiContract,
|
|
1353
4250
|
count: children.length,
|
|
1354
4251
|
defaultClasses: (0, utils_1.unique)(children.flatMap((child) => Array.isArray(child.defaultClasses)
|
|
1355
4252
|
? child.defaultClasses.filter((item) => typeof item === 'string')
|
|
@@ -1357,11 +4254,29 @@ function summarizeConnectedChildren(instances) {
|
|
|
1357
4254
|
classGroups: (0, extractors_1.mergeClassGroups)(children
|
|
1358
4255
|
.map((child) => child.classGroups)
|
|
1359
4256
|
.filter((value) => Boolean(value))),
|
|
4257
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
4258
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
4259
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
4260
|
+
variants: mergeChildVariants(children),
|
|
4261
|
+
styleHelpers: (0, utils_1.unique)(children
|
|
4262
|
+
.map((child) => child.styleHelper)
|
|
4263
|
+
.filter((value) => typeof value === 'string' && Boolean(value))).slice(0, 12),
|
|
1360
4264
|
};
|
|
1361
4265
|
})
|
|
1362
4266
|
.sort((a, b) => Number(b.count) - Number(a.count))
|
|
1363
4267
|
.slice(0, 16);
|
|
1364
4268
|
}
|
|
4269
|
+
function mergeChildVariants(children) {
|
|
4270
|
+
const variants = children
|
|
4271
|
+
.map((child) => child.variants)
|
|
4272
|
+
.filter((value) => {
|
|
4273
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
4274
|
+
return false;
|
|
4275
|
+
}
|
|
4276
|
+
return Object.values(value).every((item) => typeof item === 'string');
|
|
4277
|
+
});
|
|
4278
|
+
return (0, extractors_1.mergeVariantProps)(variants);
|
|
4279
|
+
}
|
|
1365
4280
|
function compactConnectedPatternInstance(instance) {
|
|
1366
4281
|
return {
|
|
1367
4282
|
path: instance.file.relPath,
|
|
@@ -1548,7 +4463,9 @@ function createConceptPatternDecision(name, scores, shouldReview, roleCandidateC
|
|
|
1548
4463
|
function connectedPatternVariants(domain, instances) {
|
|
1549
4464
|
const preferredPrefixes = domain === 'frontend'
|
|
1550
4465
|
? ['component.', 'route.', 'style.', 'rule.']
|
|
1551
|
-
:
|
|
4466
|
+
: domain === 'data'
|
|
4467
|
+
? ['database.', 'model.', 'service.', 'api.', 'route.']
|
|
4468
|
+
: ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.'];
|
|
1552
4469
|
return (0, utils_1.unique)(instances.flatMap((instance) => [
|
|
1553
4470
|
...instance.file.components.map((component) => (0, utils_1.toRegistryKey)('component', component)),
|
|
1554
4471
|
...instance.file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
|
|
@@ -1557,6 +4474,9 @@ function connectedPatternVariants(domain, instances) {
|
|
|
1557
4474
|
...instance.file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
|
|
1558
4475
|
...instance.file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
|
|
1559
4476
|
...instance.file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
|
|
4477
|
+
...(hasDatabaseSurface(instance.file)
|
|
4478
|
+
? [(0, utils_1.toRegistryKey)('database', instance.file.relPath.replace(/\.[^.]+$/, ''))]
|
|
4479
|
+
: []),
|
|
1560
4480
|
...instance.graphKeys.filter((key) => preferredPrefixes.some((prefix) => key.startsWith(prefix))),
|
|
1561
4481
|
])).slice(0, 80);
|
|
1562
4482
|
}
|
|
@@ -1572,6 +4492,9 @@ function connectedPatternChildren(domain, concept, instances, dependencies) {
|
|
|
1572
4492
|
const graphChildren = dependencies.filter((key) => ['component.', 'style.', 'rule.', 'route.'].some((prefix) => key.startsWith(prefix)));
|
|
1573
4493
|
return (0, utils_1.unique)([...semanticChildren, ...graphChildren]).slice(0, 80);
|
|
1574
4494
|
}
|
|
4495
|
+
if (domain === 'data') {
|
|
4496
|
+
return (0, utils_1.unique)(dependencies.filter((key) => ['database.', 'model.', 'service.', 'api.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
|
|
4497
|
+
}
|
|
1575
4498
|
return (0, utils_1.unique)(dependencies.filter((key) => ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
|
|
1576
4499
|
}
|
|
1577
4500
|
function derivePatternOwners(filePaths) {
|
|
@@ -1635,6 +4558,7 @@ function backendPatternWeight(file) {
|
|
|
1635
4558
|
file.controllers.length * 2 +
|
|
1636
4559
|
file.services.length * 2 +
|
|
1637
4560
|
file.models.length * 1.5 +
|
|
4561
|
+
file.databaseAccesses.length * 1.2 +
|
|
1638
4562
|
file.authHints.length * 0.5 +
|
|
1639
4563
|
file.events.length * 0.5 +
|
|
1640
4564
|
file.jobs.length * 0.5);
|
|
@@ -1644,6 +4568,7 @@ function hasBackendPatternSurface(file) {
|
|
|
1644
4568
|
file.controllers.length ||
|
|
1645
4569
|
file.services.length ||
|
|
1646
4570
|
file.modules.length ||
|
|
4571
|
+
file.databaseAccesses.length ||
|
|
1647
4572
|
file.apiCalls.length ||
|
|
1648
4573
|
file.middleware.length ||
|
|
1649
4574
|
file.jobs.length ||
|
|
@@ -1651,13 +4576,17 @@ function hasBackendPatternSurface(file) {
|
|
|
1651
4576
|
}
|
|
1652
4577
|
function backendPatternConcept(file) {
|
|
1653
4578
|
if (file.controllers.length) {
|
|
1654
|
-
return file.services.length || file.models.length
|
|
4579
|
+
return file.services.length || file.models.length || file.databaseAccesses.length
|
|
4580
|
+
? 'api-controller-flow'
|
|
4581
|
+
: 'api-controller';
|
|
1655
4582
|
}
|
|
1656
4583
|
if (file.apiHandlers.length) {
|
|
1657
|
-
return file.models.length || file.
|
|
4584
|
+
return file.models.length || file.databaseAccesses.length || file.authHints.length
|
|
4585
|
+
? 'api-data-flow'
|
|
4586
|
+
: 'api-route';
|
|
1658
4587
|
}
|
|
1659
4588
|
if (file.services.length) {
|
|
1660
|
-
return file.models.length ? 'service-data-flow' : 'service';
|
|
4589
|
+
return file.models.length || file.databaseAccesses.length ? 'service-data-flow' : 'service';
|
|
1661
4590
|
}
|
|
1662
4591
|
if (file.jobs.length || file.events.length) {
|
|
1663
4592
|
return 'async-flow';
|
|
@@ -1667,6 +4596,22 @@ function backendPatternConcept(file) {
|
|
|
1667
4596
|
}
|
|
1668
4597
|
return 'backend-flow';
|
|
1669
4598
|
}
|
|
4599
|
+
function databaseFieldShapeInputs(model) {
|
|
4600
|
+
return model.fields.map((field) => [
|
|
4601
|
+
field.name,
|
|
4602
|
+
field.type ?? '',
|
|
4603
|
+
field.id ? '@id' : '',
|
|
4604
|
+
field.unique ? '@unique' : '',
|
|
4605
|
+
field.optional ? '?' : '',
|
|
4606
|
+
field.list ? '[]' : '',
|
|
4607
|
+
field.default ? `default:${field.default}` : '',
|
|
4608
|
+
field.enumName ? `enum:${field.enumName}` : '',
|
|
4609
|
+
field.relation ? '@relation' : '',
|
|
4610
|
+
field.relation?.onDelete ? `onDelete:${field.relation.onDelete}` : '',
|
|
4611
|
+
]
|
|
4612
|
+
.filter(Boolean)
|
|
4613
|
+
.join(' '));
|
|
4614
|
+
}
|
|
1670
4615
|
function summarizeModelFieldShape(fields) {
|
|
1671
4616
|
const counts = {
|
|
1672
4617
|
id: 0,
|
|
@@ -1718,7 +4663,25 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
|
|
|
1718
4663
|
`Pattern score is based on usage count, file spread, component/route evidence, and structural specificity.`,
|
|
1719
4664
|
];
|
|
1720
4665
|
if (first.domain === 'frontend') {
|
|
1721
|
-
guidance.push('Use defaultClasses for normal generation. Use childStructure to keep spacing, hierarchy, and slots aligned with the source pattern.');
|
|
4666
|
+
guidance.push('Use defaultClasses for normal generation. Use childStructure to keep spacing, hierarchy, and slots aligned with the source pattern.', 'Use structureContract, parentContexts, layoutSafety, responsiveProfile, and scaleProfile as hard generation constraints before adding any new visual detail.');
|
|
4667
|
+
if (summarizeConnectedLayoutSafety(instances)) {
|
|
4668
|
+
guidance.push('Preserve layoutSafety classes with their original element. They prevent wrapped buttons, overflowing card text, collapsed columns, and mobile clipping.');
|
|
4669
|
+
}
|
|
4670
|
+
if (summarizeConnectedParentContexts(instances).length) {
|
|
4671
|
+
guidance.push('Preserve parentContexts when placing this pattern. Repeated cards and nested panels should inherit the same grid/flex parent relationship as the source.');
|
|
4672
|
+
}
|
|
4673
|
+
if (summarizeConnectedScaleProfile(instances)) {
|
|
4674
|
+
guidance.push('Preserve scaleProfile for typography, spacing, max-width, and line-height. Do not make headings or section spacing larger than observed source bounds.');
|
|
4675
|
+
}
|
|
4676
|
+
if (summarizeConnectedRoleSignatures(instances).length) {
|
|
4677
|
+
guidance.push('Preserve roleSignature and roleConstraints as hard constraints. Lookalike patterns with different role, density, surface, layout, or exactClassFacts are different generation targets.');
|
|
4678
|
+
}
|
|
4679
|
+
if (summarizeConnectedResponsiveProfile(instances)) {
|
|
4680
|
+
guidance.push('Preserve responsiveProfile exactly enough that mobile and desktop layouts use the same breakpoints and visibility behavior as the source.');
|
|
4681
|
+
}
|
|
4682
|
+
if (summarizeConnectedCompoundStructure(instances)) {
|
|
4683
|
+
guidance.push('For compound badges, hero eyebrows, and brand chips, preserve compoundStructure: icon/image children, divider children, multiple text segments, child order, compact spacing, and separate text styles. Do not collapse the lockup into a single text-only pill.');
|
|
4684
|
+
}
|
|
1722
4685
|
}
|
|
1723
4686
|
if (decorativeAccentClasses.length) {
|
|
1724
4687
|
guidance.push('decorativeAccentClasses are opt-in highlight treatments. They must not become default card/surface styling.');
|
|
@@ -1737,6 +4700,7 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
|
|
|
1737
4700
|
function connectedPatternSlug(instance) {
|
|
1738
4701
|
const parts = [
|
|
1739
4702
|
instance.concept,
|
|
4703
|
+
instance.root?.roleSignature?.role,
|
|
1740
4704
|
instance.root?.layoutRole,
|
|
1741
4705
|
instance.root?.tag,
|
|
1742
4706
|
...Object.keys(instance.root?.classGroups ?? {}).slice(0, 3),
|
|
@@ -1751,6 +4715,22 @@ function connectedPatternSlug(instance) {
|
|
|
1751
4715
|
.slice(0, 5)
|
|
1752
4716
|
.join('.') || instance.concept);
|
|
1753
4717
|
}
|
|
4718
|
+
function roleSignatureKey(signature) {
|
|
4719
|
+
if (!signature) {
|
|
4720
|
+
return 'role-signature:unspecified';
|
|
4721
|
+
}
|
|
4722
|
+
return [
|
|
4723
|
+
signature.role,
|
|
4724
|
+
signature.roleGroup,
|
|
4725
|
+
signature.scale,
|
|
4726
|
+
signature.density,
|
|
4727
|
+
signature.surface,
|
|
4728
|
+
signature.layout,
|
|
4729
|
+
signature.flags.join(','),
|
|
4730
|
+
]
|
|
4731
|
+
.filter(Boolean)
|
|
4732
|
+
.join('|');
|
|
4733
|
+
}
|
|
1754
4734
|
function classSignature(classes) {
|
|
1755
4735
|
if (!classes.length) {
|
|
1756
4736
|
return 'no-classes';
|
|
@@ -1771,7 +4751,7 @@ function normalizeTagForSignature(element) {
|
|
|
1771
4751
|
return element.originalTag.toLowerCase();
|
|
1772
4752
|
}
|
|
1773
4753
|
function normalizeChildTagForSignature(child) {
|
|
1774
|
-
if (['button', 'input', 'badge', 'heading', 'text', 'link'].includes(child.kind)) {
|
|
4754
|
+
if (['button', 'input', 'badge', 'heading', 'text', 'link', 'icon', 'image', 'divider'].includes(child.kind)) {
|
|
1775
4755
|
return child.kind;
|
|
1776
4756
|
}
|
|
1777
4757
|
return child.originalTag.toLowerCase();
|
|
@@ -1840,6 +4820,30 @@ function addStyleUsageEdges(entries, files, edges) {
|
|
|
1840
4820
|
}
|
|
1841
4821
|
}
|
|
1842
4822
|
}
|
|
4823
|
+
function addDatabaseUsageEdges(entries, files, edges) {
|
|
4824
|
+
const modelEntries = entries.filter((entry) => entry.group === 'model');
|
|
4825
|
+
const modelKeysByName = new Map();
|
|
4826
|
+
for (const entry of modelEntries) {
|
|
4827
|
+
const name = typeof entry.value.name === 'string' ? entry.value.name : '';
|
|
4828
|
+
const tableName = typeof entry.value.tableName === 'string' ? entry.value.tableName : '';
|
|
4829
|
+
if (name) {
|
|
4830
|
+
modelKeysByName.set(name.toLowerCase(), entry.key);
|
|
4831
|
+
}
|
|
4832
|
+
if (tableName) {
|
|
4833
|
+
modelKeysByName.set(tableName.toLowerCase(), entry.key);
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
for (const file of files) {
|
|
4837
|
+
for (const access of file.databaseAccesses) {
|
|
4838
|
+
const targetName = access.model ?? access.table;
|
|
4839
|
+
const targetKey = targetName ? modelKeysByName.get(targetName.toLowerCase()) : undefined;
|
|
4840
|
+
if (!targetKey) {
|
|
4841
|
+
continue;
|
|
4842
|
+
}
|
|
4843
|
+
edges.push(toGraphEdge(file.key, targetKey, databaseAccessEdgeType(access), `database.${access.framework}.access`, file, access.confidence));
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
1843
4847
|
function addModelUsageEdges(entries, files, edges) {
|
|
1844
4848
|
const modelEntries = entries.filter((entry) => entry.group === 'model');
|
|
1845
4849
|
for (const file of files) {
|
|
@@ -1870,6 +4874,50 @@ function addModelUsageEdges(entries, files, edges) {
|
|
|
1870
4874
|
}
|
|
1871
4875
|
}
|
|
1872
4876
|
}
|
|
4877
|
+
function addDatabaseReviewItems(entries, reviewItems) {
|
|
4878
|
+
const modelEntries = entries.filter((entry) => entry.group === 'model');
|
|
4879
|
+
const byName = (0, utils_1.groupBy)(modelEntries, (entry) => String(entry.value.name ?? entry.key).toLowerCase());
|
|
4880
|
+
for (const [name, candidates] of byName) {
|
|
4881
|
+
if (candidates.length < 2) {
|
|
4882
|
+
continue;
|
|
4883
|
+
}
|
|
4884
|
+
const signatures = (0, utils_1.unique)(candidates.map((candidate) => {
|
|
4885
|
+
const fields = Array.isArray(candidate.value.fields)
|
|
4886
|
+
? candidate.value.fields.map(String).sort()
|
|
4887
|
+
: [];
|
|
4888
|
+
const provider = String(candidate.value.provider ?? '');
|
|
4889
|
+
const tableName = String(candidate.value.tableName ?? '');
|
|
4890
|
+
return `${provider}:${tableName}:${fields.join('|')}`;
|
|
4891
|
+
}));
|
|
4892
|
+
if (signatures.length <= 1) {
|
|
4893
|
+
continue;
|
|
4894
|
+
}
|
|
4895
|
+
reviewItems.push({
|
|
4896
|
+
id: `review.data.model.${name}.conflict`,
|
|
4897
|
+
type: 'concept_collision',
|
|
4898
|
+
status: 'open',
|
|
4899
|
+
group: 'model',
|
|
4900
|
+
s: `Multiple ${name} data model shapes were found. Confirm the canonical model/table before broad generation.`,
|
|
4901
|
+
candidates: candidates.map((candidate) => candidate.key).slice(0, 40),
|
|
4902
|
+
});
|
|
4903
|
+
}
|
|
4904
|
+
const riskyAccessCandidates = entries
|
|
4905
|
+
.filter((entry) => entry.group === 'database' &&
|
|
4906
|
+
entry.value.kind === 'database-access' &&
|
|
4907
|
+
Array.isArray(entry.value.risk) &&
|
|
4908
|
+
entry.value.risk.includes('manual-review'))
|
|
4909
|
+
.map((entry) => entry.key);
|
|
4910
|
+
if (riskyAccessCandidates.length) {
|
|
4911
|
+
reviewItems.push({
|
|
4912
|
+
id: 'review.data.access.manual-review',
|
|
4913
|
+
type: 'low_confidence',
|
|
4914
|
+
status: 'open',
|
|
4915
|
+
group: 'database',
|
|
4916
|
+
s: 'Raw SQL or high-risk database accesses were found. Review before using them as broad generation patterns.',
|
|
4917
|
+
candidates: riskyAccessCandidates.slice(0, 40),
|
|
4918
|
+
});
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
1873
4921
|
function addConceptClusters(entries, usedKeys, nodes, edges, reviewItems) {
|
|
1874
4922
|
const concepts = [
|
|
1875
4923
|
{
|
|
@@ -2082,9 +5130,25 @@ function detectProjectFacts(files) {
|
|
|
2082
5130
|
}
|
|
2083
5131
|
if (dependencies.has('prisma') ||
|
|
2084
5132
|
dependencies.has('@prisma/client') ||
|
|
2085
|
-
files.some((file) => file.
|
|
5133
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'prisma'))) {
|
|
2086
5134
|
frameworks.add('prisma');
|
|
2087
5135
|
}
|
|
5136
|
+
if (dependencies.has('drizzle-orm') ||
|
|
5137
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'drizzle'))) {
|
|
5138
|
+
frameworks.add('drizzle');
|
|
5139
|
+
}
|
|
5140
|
+
if (dependencies.has('typeorm') ||
|
|
5141
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'typeorm'))) {
|
|
5142
|
+
frameworks.add('typeorm');
|
|
5143
|
+
}
|
|
5144
|
+
if (dependencies.has('sequelize') ||
|
|
5145
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'sequelize'))) {
|
|
5146
|
+
frameworks.add('sequelize');
|
|
5147
|
+
}
|
|
5148
|
+
if (dependencies.has('mongoose') ||
|
|
5149
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'mongoose'))) {
|
|
5150
|
+
frameworks.add('mongoose');
|
|
5151
|
+
}
|
|
2088
5152
|
if (files.some((file) => file.language === 'css' || file.language === 'scss')) {
|
|
2089
5153
|
frameworks.add('css');
|
|
2090
5154
|
}
|
|
@@ -2119,7 +5183,9 @@ function detectProjectFacts(files) {
|
|
|
2119
5183
|
if (files.some((file) => file.language === 'go')) {
|
|
2120
5184
|
frameworks.add('go');
|
|
2121
5185
|
}
|
|
2122
|
-
if (files.some((file) => file.language === 'sql'
|
|
5186
|
+
if (files.some((file) => file.language === 'sql' ||
|
|
5187
|
+
file.databaseModels.some((model) => model.provider === 'sql') ||
|
|
5188
|
+
file.databaseMigrations.some((migration) => migration.provider === 'sql'))) {
|
|
2123
5189
|
frameworks.add('sql');
|
|
2124
5190
|
}
|
|
2125
5191
|
return {
|
|
@@ -2137,8 +5203,10 @@ function detectFrameworkForFile(file) {
|
|
|
2137
5203
|
if (file.controllers.length || file.services.length || file.modules.length) {
|
|
2138
5204
|
return 'nestjs';
|
|
2139
5205
|
}
|
|
2140
|
-
if (file.
|
|
2141
|
-
return
|
|
5206
|
+
if (file.databaseModels.length || file.databaseEnums.length || file.databaseMigrations.length) {
|
|
5207
|
+
return (file.databaseModels[0]?.provider ??
|
|
5208
|
+
file.databaseEnums[0]?.provider ??
|
|
5209
|
+
file.databaseMigrations[0]?.provider);
|
|
2142
5210
|
}
|
|
2143
5211
|
if (file.kind.includes('style') || file.kind.includes('css')) {
|
|
2144
5212
|
return 'css';
|