@schemyx/mcp 0.1.1 → 0.1.2
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 +3127 -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 +55 -1
- package/dist/codebase-scanner/extractors.js +931 -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 +2 -2
- package/dist/codebase-scanner/recipes.js +159 -1
- package/dist/codebase-scanner/recipes.js.map +1 -1
- package/dist/codebase-scanner/types.d.ts +221 -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,1873 @@ 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-rows-(\d+)$/.test(base)) {
|
|
1645
|
+
const [, value] = base.match(/^grid-rows-(\d+)$/) ?? [];
|
|
1646
|
+
declarations.push(declaration('grid-template-rows', `repeat(${value}, minmax(0, 1fr))`));
|
|
1647
|
+
}
|
|
1648
|
+
if (/^col-span-(\d+)$/.test(base)) {
|
|
1649
|
+
const [, value] = base.match(/^col-span-(\d+)$/) ?? [];
|
|
1650
|
+
declarations.push(declaration('grid-column', `span ${value} / span ${value}`));
|
|
1651
|
+
}
|
|
1652
|
+
if (/^row-span-(\d+)$/.test(base)) {
|
|
1653
|
+
const [, value] = base.match(/^row-span-(\d+)$/) ?? [];
|
|
1654
|
+
declarations.push(declaration('grid-row', `span ${value} / span ${value}`));
|
|
1655
|
+
}
|
|
1656
|
+
if (/^(?:relative|absolute|fixed|sticky|static)$/.test(base)) {
|
|
1657
|
+
declarations.push(declaration('position', base));
|
|
1658
|
+
}
|
|
1659
|
+
if (/^(?:inset|top|right|bottom|left)-(.+)$/.test(base)) {
|
|
1660
|
+
const [, property, token] = base.match(/^(inset|top|right|bottom|left)-(.+)$/) ?? [];
|
|
1661
|
+
const value = tailwindScaleValue(token);
|
|
1662
|
+
if (value)
|
|
1663
|
+
declarations.push(declaration(property, value));
|
|
1664
|
+
}
|
|
1665
|
+
if (/^z-\[([^\]]+)\]$/.test(base)) {
|
|
1666
|
+
declarations.push(declaration('z-index', arbitraryValue(base)));
|
|
1667
|
+
}
|
|
1668
|
+
if (/^z-(\d+)$/.test(base)) {
|
|
1669
|
+
const [, value] = base.match(/^z-(\d+)$/) ?? [];
|
|
1670
|
+
declarations.push(declaration('z-index', value));
|
|
1671
|
+
}
|
|
1672
|
+
return declarations;
|
|
1673
|
+
}
|
|
1674
|
+
function tailwindAlignmentDeclaration(base) {
|
|
1675
|
+
const alignItems = {
|
|
1676
|
+
'items-start': 'flex-start',
|
|
1677
|
+
'items-end': 'flex-end',
|
|
1678
|
+
'items-center': 'center',
|
|
1679
|
+
'items-baseline': 'baseline',
|
|
1680
|
+
'items-stretch': 'stretch',
|
|
1681
|
+
};
|
|
1682
|
+
const justifyContent = {
|
|
1683
|
+
'justify-start': 'flex-start',
|
|
1684
|
+
'justify-end': 'flex-end',
|
|
1685
|
+
'justify-center': 'center',
|
|
1686
|
+
'justify-between': 'space-between',
|
|
1687
|
+
'justify-around': 'space-around',
|
|
1688
|
+
'justify-evenly': 'space-evenly',
|
|
1689
|
+
};
|
|
1690
|
+
if (alignItems[base])
|
|
1691
|
+
return { property: 'align-items', value: alignItems[base] };
|
|
1692
|
+
if (justifyContent[base])
|
|
1693
|
+
return { property: 'justify-content', value: justifyContent[base] };
|
|
1694
|
+
return undefined;
|
|
1695
|
+
}
|
|
1696
|
+
function tailwindSpacingDeclaration(base) {
|
|
1697
|
+
const spacingMatch = base.match(/^(p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y)-(.+)$/);
|
|
1698
|
+
if (!spacingMatch) {
|
|
1699
|
+
return [];
|
|
1700
|
+
}
|
|
1701
|
+
const [, prefix, token] = spacingMatch;
|
|
1702
|
+
const value = token === 'auto' ? 'auto' : tailwindScaleValue(token);
|
|
1703
|
+
if (!value) {
|
|
1704
|
+
return [];
|
|
1705
|
+
}
|
|
1706
|
+
const properties = {
|
|
1707
|
+
p: ['padding'],
|
|
1708
|
+
px: ['padding-left', 'padding-right'],
|
|
1709
|
+
py: ['padding-top', 'padding-bottom'],
|
|
1710
|
+
pt: ['padding-top'],
|
|
1711
|
+
pr: ['padding-right'],
|
|
1712
|
+
pb: ['padding-bottom'],
|
|
1713
|
+
pl: ['padding-left'],
|
|
1714
|
+
m: ['margin'],
|
|
1715
|
+
mx: ['margin-left', 'margin-right'],
|
|
1716
|
+
my: ['margin-top', 'margin-bottom'],
|
|
1717
|
+
mt: ['margin-top'],
|
|
1718
|
+
mr: ['margin-right'],
|
|
1719
|
+
mb: ['margin-bottom'],
|
|
1720
|
+
ml: ['margin-left'],
|
|
1721
|
+
gap: ['gap'],
|
|
1722
|
+
'gap-x': ['column-gap'],
|
|
1723
|
+
'gap-y': ['row-gap'],
|
|
1724
|
+
};
|
|
1725
|
+
return (properties[prefix] ?? []).map((property) => ({ property, value }));
|
|
1726
|
+
}
|
|
1727
|
+
function tailwindSizingDeclaration(base) {
|
|
1728
|
+
if (base === 'min-w-0')
|
|
1729
|
+
return [{ property: 'min-width', value: '0px' }];
|
|
1730
|
+
if (base === 'min-h-0')
|
|
1731
|
+
return [{ property: 'min-height', value: '0px' }];
|
|
1732
|
+
if (base === 'w-px')
|
|
1733
|
+
return [{ property: 'width', value: '1px' }];
|
|
1734
|
+
if (base === 'h-px')
|
|
1735
|
+
return [{ property: 'height', value: '1px' }];
|
|
1736
|
+
if (base === 'w-full')
|
|
1737
|
+
return [{ property: 'width', value: '100%' }];
|
|
1738
|
+
if (base === 'h-full')
|
|
1739
|
+
return [{ property: 'height', value: '100%' }];
|
|
1740
|
+
if (base === 'max-w-none')
|
|
1741
|
+
return [{ property: 'max-width', value: 'none' }];
|
|
1742
|
+
if (base === 'shrink-0')
|
|
1743
|
+
return [{ property: 'flex-shrink', value: '0' }];
|
|
1744
|
+
if (base === 'shrink')
|
|
1745
|
+
return [{ property: 'flex-shrink', value: '1' }];
|
|
1746
|
+
if (base === 'grow')
|
|
1747
|
+
return [{ property: 'flex-grow', value: '1' }];
|
|
1748
|
+
if (base === 'grow-0')
|
|
1749
|
+
return [{ property: 'flex-grow', value: '0' }];
|
|
1750
|
+
const sizeMatch = base.match(/^(size|w|h|min-w|min-h|max-w|max-h)-(.+)$/);
|
|
1751
|
+
if (!sizeMatch) {
|
|
1752
|
+
return [];
|
|
1753
|
+
}
|
|
1754
|
+
const [, prefix, token] = sizeMatch;
|
|
1755
|
+
const value = tailwindScaleValue(token) ?? tailwindNamedSizeValue(token);
|
|
1756
|
+
if (!value) {
|
|
1757
|
+
return [];
|
|
1758
|
+
}
|
|
1759
|
+
const properties = {
|
|
1760
|
+
size: ['width', 'height'],
|
|
1761
|
+
w: ['width'],
|
|
1762
|
+
h: ['height'],
|
|
1763
|
+
'min-w': ['min-width'],
|
|
1764
|
+
'min-h': ['min-height'],
|
|
1765
|
+
'max-w': ['max-width'],
|
|
1766
|
+
'max-h': ['max-height'],
|
|
1767
|
+
};
|
|
1768
|
+
return (properties[prefix] ?? []).map((property) => ({ property, value }));
|
|
1769
|
+
}
|
|
1770
|
+
function tailwindTypographyDeclaration(base) {
|
|
1771
|
+
const declarations = [];
|
|
1772
|
+
const textMatch = base.match(/^text-(.+)$/);
|
|
1773
|
+
if (textMatch) {
|
|
1774
|
+
const token = textMatch[1];
|
|
1775
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1776
|
+
if (arbitrary && looksLikeCssLength(arbitrary)) {
|
|
1777
|
+
declarations.push({ property: 'font-size', value: arbitrary });
|
|
1778
|
+
}
|
|
1779
|
+
else if (tailwindFontSizeScale[token]) {
|
|
1780
|
+
declarations.push({ property: 'font-size', value: tailwindFontSizeScale[token] });
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
const weightMatch = base.match(/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/);
|
|
1784
|
+
if (weightMatch)
|
|
1785
|
+
declarations.push({ property: 'font-weight', value: tailwindFontWeightScale[weightMatch[1]] });
|
|
1786
|
+
const leadingMatch = base.match(/^leading-(.+)$/);
|
|
1787
|
+
if (leadingMatch) {
|
|
1788
|
+
const token = leadingMatch[1];
|
|
1789
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1790
|
+
const value = arbitrary ?? tailwindLineHeightScale[token] ?? tailwindScaleValue(token);
|
|
1791
|
+
if (value)
|
|
1792
|
+
declarations.push({ property: 'line-height', value });
|
|
1793
|
+
}
|
|
1794
|
+
const trackingMatch = base.match(/^tracking-(.+)$/);
|
|
1795
|
+
if (trackingMatch) {
|
|
1796
|
+
const token = trackingMatch[1];
|
|
1797
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1798
|
+
const value = arbitrary ?? tailwindTrackingScale[token];
|
|
1799
|
+
if (value)
|
|
1800
|
+
declarations.push({ property: 'letter-spacing', value });
|
|
1801
|
+
}
|
|
1802
|
+
if (base === 'uppercase')
|
|
1803
|
+
declarations.push({ property: 'text-transform', value: 'uppercase' });
|
|
1804
|
+
if (base === 'lowercase')
|
|
1805
|
+
declarations.push({ property: 'text-transform', value: 'lowercase' });
|
|
1806
|
+
if (base === 'capitalize')
|
|
1807
|
+
declarations.push({ property: 'text-transform', value: 'capitalize' });
|
|
1808
|
+
if (base === 'normal-case')
|
|
1809
|
+
declarations.push({ property: 'text-transform', value: 'none' });
|
|
1810
|
+
if (base === 'text-left')
|
|
1811
|
+
declarations.push({ property: 'text-align', value: 'left' });
|
|
1812
|
+
if (base === 'text-center')
|
|
1813
|
+
declarations.push({ property: 'text-align', value: 'center' });
|
|
1814
|
+
if (base === 'text-right')
|
|
1815
|
+
declarations.push({ property: 'text-align', value: 'right' });
|
|
1816
|
+
return declarations;
|
|
1817
|
+
}
|
|
1818
|
+
function tailwindBorderDeclaration(base, styleContext) {
|
|
1819
|
+
if (base === 'border')
|
|
1820
|
+
return [{ property: 'border-width', value: '1px' }];
|
|
1821
|
+
if (/^border-(\d+)$/.test(base)) {
|
|
1822
|
+
const [, value] = base.match(/^border-(\d+)$/) ?? [];
|
|
1823
|
+
return [{ property: 'border-width', value: `${value}px` }];
|
|
1824
|
+
}
|
|
1825
|
+
const roundedMatch = base.match(/^rounded(?:-(.+))?$/);
|
|
1826
|
+
if (roundedMatch) {
|
|
1827
|
+
const token = roundedMatch[1] ?? 'DEFAULT';
|
|
1828
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1829
|
+
const value = arbitrary ?? tailwindRadiusScale[token] ?? cssTokenValue('radius', styleContext);
|
|
1830
|
+
return value ? [{ property: 'border-radius', value }] : [];
|
|
1831
|
+
}
|
|
1832
|
+
if (base.startsWith('border-') && !/^border-(?:t|r|b|l|x|y)$/.test(base)) {
|
|
1833
|
+
return [{ property: 'border-color', value: tailwindColorValue(base.slice('border-'.length), styleContext) }];
|
|
1834
|
+
}
|
|
1835
|
+
return [];
|
|
1836
|
+
}
|
|
1837
|
+
function tailwindColorDeclaration(base, styleContext) {
|
|
1838
|
+
const bgMatch = base.match(/^bg-(.+)$/);
|
|
1839
|
+
if (bgMatch) {
|
|
1840
|
+
return [{ property: 'background-color', value: tailwindColorValue(bgMatch[1], styleContext) }];
|
|
1841
|
+
}
|
|
1842
|
+
const textMatch = base.match(/^text-(.+)$/);
|
|
1843
|
+
if (textMatch && !tailwindFontSizeScale[textMatch[1]] && !looksLikeCssLength(arbitraryTokenValue(textMatch[1]) ?? '')) {
|
|
1844
|
+
return [{ property: 'color', value: tailwindColorValue(textMatch[1], styleContext) }];
|
|
1845
|
+
}
|
|
1846
|
+
const fillMatch = base.match(/^fill-(.+)$/);
|
|
1847
|
+
if (fillMatch)
|
|
1848
|
+
return [{ property: 'fill', value: tailwindColorValue(fillMatch[1], styleContext) }];
|
|
1849
|
+
const strokeMatch = base.match(/^stroke-(.+)$/);
|
|
1850
|
+
if (strokeMatch)
|
|
1851
|
+
return [{ property: 'stroke', value: tailwindColorValue(strokeMatch[1], styleContext) }];
|
|
1852
|
+
return [];
|
|
1853
|
+
}
|
|
1854
|
+
function tailwindColorValue(token, styleContext) {
|
|
1855
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1856
|
+
if (arbitrary) {
|
|
1857
|
+
return resolveCssValueTokens(arbitrary.replace(/_/g, ' '), styleContext?.cssVariables);
|
|
1858
|
+
}
|
|
1859
|
+
const [name, opacity] = token.split('/');
|
|
1860
|
+
const cssVariable = cssTokenValue(name, styleContext);
|
|
1861
|
+
const baseValue = cssVariable ?? colorKeywordValue(name) ?? `token(${name})`;
|
|
1862
|
+
return opacity ? `${baseValue} / ${opacityToPercent(opacity)}` : baseValue;
|
|
1863
|
+
}
|
|
1864
|
+
function cssTokenValue(token, styleContext) {
|
|
1865
|
+
const candidates = (0, utils_1.unique)([`--${token}`, `--color-${token}`, `--${token.replace(/\//g, '-')}`]);
|
|
1866
|
+
const match = candidates.find((candidate) => styleContext?.cssVariables.has(candidate));
|
|
1867
|
+
return match ? `var(${match})` : undefined;
|
|
1868
|
+
}
|
|
1869
|
+
function colorKeywordValue(token) {
|
|
1870
|
+
const colors = {
|
|
1871
|
+
transparent: 'transparent',
|
|
1872
|
+
current: 'currentColor',
|
|
1873
|
+
black: '#000000',
|
|
1874
|
+
white: '#ffffff',
|
|
1875
|
+
};
|
|
1876
|
+
return colors[token];
|
|
1877
|
+
}
|
|
1878
|
+
function opacityToPercent(value) {
|
|
1879
|
+
const numeric = Number(value);
|
|
1880
|
+
if (Number.isFinite(numeric)) {
|
|
1881
|
+
return `${numeric}%`;
|
|
1882
|
+
}
|
|
1883
|
+
return value;
|
|
1884
|
+
}
|
|
1885
|
+
function tailwindScaleValue(token) {
|
|
1886
|
+
const arbitrary = arbitraryTokenValue(token);
|
|
1887
|
+
if (arbitrary) {
|
|
1888
|
+
return arbitrary.replace(/_/g, ' ');
|
|
1889
|
+
}
|
|
1890
|
+
const negative = token.startsWith('-');
|
|
1891
|
+
const lookup = negative ? token.slice(1) : token;
|
|
1892
|
+
const value = tailwindSpacingScale[lookup];
|
|
1893
|
+
return value ? `${negative ? '-' : ''}${value}` : undefined;
|
|
1894
|
+
}
|
|
1895
|
+
function tailwindNamedSizeValue(token) {
|
|
1896
|
+
const values = {
|
|
1897
|
+
auto: 'auto',
|
|
1898
|
+
full: '100%',
|
|
1899
|
+
screen: '100vw',
|
|
1900
|
+
svw: '100svw',
|
|
1901
|
+
lvw: '100lvw',
|
|
1902
|
+
dvw: '100dvw',
|
|
1903
|
+
min: 'min-content',
|
|
1904
|
+
max: 'max-content',
|
|
1905
|
+
fit: 'fit-content',
|
|
1906
|
+
prose: '65ch',
|
|
1907
|
+
};
|
|
1908
|
+
return values[token];
|
|
1909
|
+
}
|
|
1910
|
+
function arbitraryTokenValue(token) {
|
|
1911
|
+
const match = token.match(/^\[(.+)\]$/);
|
|
1912
|
+
return match?.[1];
|
|
1913
|
+
}
|
|
1914
|
+
function arbitraryValue(base) {
|
|
1915
|
+
return base.match(/\[(.+)\]/)?.[1] ?? '';
|
|
1916
|
+
}
|
|
1917
|
+
function looksLikeCssLength(value) {
|
|
1918
|
+
return /^-?(?:\d|\.)/.test(value) || /^var\(--/.test(value) || /^clamp\(/.test(value);
|
|
1919
|
+
}
|
|
1920
|
+
function tailwindShadowValue(base) {
|
|
1921
|
+
const arbitrary = arbitraryValue(base);
|
|
1922
|
+
if (arbitrary) {
|
|
1923
|
+
return arbitrary.replace(/_/g, ' ');
|
|
1924
|
+
}
|
|
1925
|
+
if (base === 'shadow-none') {
|
|
1926
|
+
return 'none';
|
|
1927
|
+
}
|
|
1928
|
+
return `tailwind(${base})`;
|
|
1929
|
+
}
|
|
1930
|
+
function bootstrapDeclarationsForClass(className, styleContext) {
|
|
1931
|
+
const base = (0, utils_1.classBase)(className).replace(/^!/, '');
|
|
1932
|
+
if (!isBootstrapClass(base)) {
|
|
1933
|
+
return [];
|
|
1934
|
+
}
|
|
1935
|
+
const prefixes = bootstrapResponsivePrefixes(base);
|
|
1936
|
+
const normalized = stripBootstrapResponsiveInfix(base);
|
|
1937
|
+
const declaration = (property, value) => ({
|
|
1938
|
+
property,
|
|
1939
|
+
value: resolveCssValueTokens(value, styleContext?.cssVariables),
|
|
1940
|
+
source: 'bootstrap-utility',
|
|
1941
|
+
styleSystem: 'bootstrap',
|
|
1942
|
+
className,
|
|
1943
|
+
prefixes,
|
|
1944
|
+
cascadeRank: 20,
|
|
1945
|
+
});
|
|
1946
|
+
const declarations = [];
|
|
1947
|
+
declarations.push(...bootstrapComponentDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1948
|
+
declarations.push(...bootstrapSpacingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1949
|
+
declarations.push(...bootstrapDisplayDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1950
|
+
declarations.push(...bootstrapFlexDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1951
|
+
declarations.push(...bootstrapSizingDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1952
|
+
declarations.push(...bootstrapTypographyDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1953
|
+
declarations.push(...bootstrapColorDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1954
|
+
declarations.push(...bootstrapBorderDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1955
|
+
declarations.push(...bootstrapGridDeclarations(normalized).map((item) => declaration(item.property, item.value)));
|
|
1956
|
+
return declarations;
|
|
1957
|
+
}
|
|
1958
|
+
function isBootstrapClass(className) {
|
|
1959
|
+
const base = stripBootstrapResponsiveInfix((0, utils_1.classBase)(className));
|
|
1960
|
+
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);
|
|
1961
|
+
}
|
|
1962
|
+
function bootstrapResponsivePrefixes(className) {
|
|
1963
|
+
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))-/);
|
|
1964
|
+
return match ? [match[1]] : [];
|
|
1965
|
+
}
|
|
1966
|
+
function stripBootstrapResponsiveInfix(className) {
|
|
1967
|
+
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-');
|
|
1968
|
+
}
|
|
1969
|
+
function bootstrapComponentDeclarations(base) {
|
|
1970
|
+
const declarations = [];
|
|
1971
|
+
if (base === 'container' || base === 'container-fluid') {
|
|
1972
|
+
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' });
|
|
1973
|
+
}
|
|
1974
|
+
if (base === 'row') {
|
|
1975
|
+
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))' });
|
|
1976
|
+
}
|
|
1977
|
+
if (base === 'card') {
|
|
1978
|
+
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)' });
|
|
1979
|
+
}
|
|
1980
|
+
if (base === 'card-body') {
|
|
1981
|
+
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)' });
|
|
1982
|
+
}
|
|
1983
|
+
if (base === 'btn') {
|
|
1984
|
+
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)' });
|
|
1985
|
+
}
|
|
1986
|
+
if (base.startsWith('btn-') && base !== 'btn-link') {
|
|
1987
|
+
const tone = base.replace(/^btn-outline-/, '').replace(/^btn-/, '');
|
|
1988
|
+
const outline = base.startsWith('btn-outline-');
|
|
1989
|
+
declarations.push({ property: 'color', value: outline ? `var(--bs-${tone})` : `var(--bs-btn-color)` }, {
|
|
1990
|
+
property: 'background-color',
|
|
1991
|
+
value: outline ? 'transparent' : `var(--bs-${tone})`,
|
|
1992
|
+
}, { property: 'border-color', value: `var(--bs-${tone})` });
|
|
1993
|
+
}
|
|
1994
|
+
if (base === 'btn-link') {
|
|
1995
|
+
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' });
|
|
1996
|
+
}
|
|
1997
|
+
if (base === 'badge') {
|
|
1998
|
+
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)' });
|
|
1999
|
+
}
|
|
2000
|
+
if (base === 'form-control' || base === 'form-select') {
|
|
2001
|
+
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)' });
|
|
2002
|
+
}
|
|
2003
|
+
if (base === 'form-label') {
|
|
2004
|
+
declarations.push({ property: 'margin-bottom', value: '0.5rem' }, { property: 'display', value: 'inline-block' });
|
|
2005
|
+
}
|
|
2006
|
+
if (base === 'navbar') {
|
|
2007
|
+
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)' });
|
|
2008
|
+
}
|
|
2009
|
+
if (base === 'nav') {
|
|
2010
|
+
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' });
|
|
2011
|
+
}
|
|
2012
|
+
if (base === 'nav-link') {
|
|
2013
|
+
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' });
|
|
2014
|
+
}
|
|
2015
|
+
if (base === 'alert') {
|
|
2016
|
+
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)' });
|
|
2017
|
+
}
|
|
2018
|
+
return declarations;
|
|
2019
|
+
}
|
|
2020
|
+
function bootstrapSpacingDeclarations(base) {
|
|
2021
|
+
const match = base.match(/^(m|p)([stebxy]?)-([0-5]|auto)$/);
|
|
2022
|
+
if (!match) {
|
|
2023
|
+
return [];
|
|
2024
|
+
}
|
|
2025
|
+
const [, kind, direction, token] = match;
|
|
2026
|
+
const value = bootstrapSpacingScale[token];
|
|
2027
|
+
const propertyRoot = kind === 'm' ? 'margin' : 'padding';
|
|
2028
|
+
const directionMap = {
|
|
2029
|
+
'': [propertyRoot],
|
|
2030
|
+
t: [`${propertyRoot}-top`],
|
|
2031
|
+
e: [`${propertyRoot}-right`],
|
|
2032
|
+
b: [`${propertyRoot}-bottom`],
|
|
2033
|
+
s: [`${propertyRoot}-left`],
|
|
2034
|
+
x: [`${propertyRoot}-left`, `${propertyRoot}-right`],
|
|
2035
|
+
y: [`${propertyRoot}-top`, `${propertyRoot}-bottom`],
|
|
2036
|
+
};
|
|
2037
|
+
return (directionMap[direction] ?? []).map((property) => ({ property, value }));
|
|
2038
|
+
}
|
|
2039
|
+
function bootstrapDisplayDeclarations(base) {
|
|
2040
|
+
const match = base.match(/^d-(none|inline|inline-block|block|grid|inline-grid|table|table-row|table-cell|flex|inline-flex)$/);
|
|
2041
|
+
return match ? [{ property: 'display', value: match[1] }] : [];
|
|
2042
|
+
}
|
|
2043
|
+
function bootstrapFlexDeclarations(base) {
|
|
2044
|
+
const declarations = [];
|
|
2045
|
+
const justifyMap = {
|
|
2046
|
+
start: 'flex-start',
|
|
2047
|
+
end: 'flex-end',
|
|
2048
|
+
center: 'center',
|
|
2049
|
+
between: 'space-between',
|
|
2050
|
+
around: 'space-around',
|
|
2051
|
+
evenly: 'space-evenly',
|
|
2052
|
+
};
|
|
2053
|
+
const alignMap = {
|
|
2054
|
+
start: 'flex-start',
|
|
2055
|
+
end: 'flex-end',
|
|
2056
|
+
center: 'center',
|
|
2057
|
+
baseline: 'baseline',
|
|
2058
|
+
stretch: 'stretch',
|
|
2059
|
+
};
|
|
2060
|
+
if (base === 'flex-row')
|
|
2061
|
+
declarations.push({ property: 'flex-direction', value: 'row' });
|
|
2062
|
+
if (base === 'flex-column')
|
|
2063
|
+
declarations.push({ property: 'flex-direction', value: 'column' });
|
|
2064
|
+
if (base === 'flex-wrap')
|
|
2065
|
+
declarations.push({ property: 'flex-wrap', value: 'wrap' });
|
|
2066
|
+
if (base === 'flex-nowrap')
|
|
2067
|
+
declarations.push({ property: 'flex-wrap', value: 'nowrap' });
|
|
2068
|
+
const justify = base.match(/^justify-content-(start|end|center|between|around|evenly)$/);
|
|
2069
|
+
if (justify)
|
|
2070
|
+
declarations.push({ property: 'justify-content', value: justifyMap[justify[1]] });
|
|
2071
|
+
const alignItems = base.match(/^align-items-(start|end|center|baseline|stretch)$/);
|
|
2072
|
+
if (alignItems)
|
|
2073
|
+
declarations.push({ property: 'align-items', value: alignMap[alignItems[1]] });
|
|
2074
|
+
const alignSelf = base.match(/^align-self-(start|end|center|baseline|stretch)$/);
|
|
2075
|
+
if (alignSelf)
|
|
2076
|
+
declarations.push({ property: 'align-self', value: alignMap[alignSelf[1]] });
|
|
2077
|
+
return declarations;
|
|
2078
|
+
}
|
|
2079
|
+
function bootstrapSizingDeclarations(base) {
|
|
2080
|
+
const percentMatch = base.match(/^(w|h|mw|mh)-(\d+)$/);
|
|
2081
|
+
if (percentMatch) {
|
|
2082
|
+
const [, prefix, amount] = percentMatch;
|
|
2083
|
+
const propertyMap = {
|
|
2084
|
+
w: 'width',
|
|
2085
|
+
h: 'height',
|
|
2086
|
+
mw: 'max-width',
|
|
2087
|
+
mh: 'max-height',
|
|
2088
|
+
};
|
|
2089
|
+
return [{ property: propertyMap[prefix], value: `${amount}%` }];
|
|
2090
|
+
}
|
|
2091
|
+
if (base === 'min-vh-100')
|
|
2092
|
+
return [{ property: 'min-height', value: '100vh' }];
|
|
2093
|
+
if (base === 'min-vw-100')
|
|
2094
|
+
return [{ property: 'min-width', value: '100vw' }];
|
|
2095
|
+
if (base === 'vh-100')
|
|
2096
|
+
return [{ property: 'height', value: '100vh' }];
|
|
2097
|
+
if (base === 'vw-100')
|
|
2098
|
+
return [{ property: 'width', value: '100vw' }];
|
|
2099
|
+
return [];
|
|
2100
|
+
}
|
|
2101
|
+
function bootstrapTypographyDeclarations(base) {
|
|
2102
|
+
const declarations = [];
|
|
2103
|
+
const fsMatch = base.match(/^fs-([1-6])$/);
|
|
2104
|
+
if (fsMatch)
|
|
2105
|
+
declarations.push({ property: 'font-size', value: bootstrapFontSizeScale[fsMatch[1]] });
|
|
2106
|
+
const fwMap = {
|
|
2107
|
+
lighter: 'lighter',
|
|
2108
|
+
light: '300',
|
|
2109
|
+
normal: '400',
|
|
2110
|
+
semibold: '600',
|
|
2111
|
+
bold: '700',
|
|
2112
|
+
bolder: 'bolder',
|
|
2113
|
+
};
|
|
2114
|
+
const fwMatch = base.match(/^fw-(lighter|light|normal|semibold|bold|bolder)$/);
|
|
2115
|
+
if (fwMatch)
|
|
2116
|
+
declarations.push({ property: 'font-weight', value: fwMap[fwMatch[1]] });
|
|
2117
|
+
const textAlign = base.match(/^text-(start|end|center)$/);
|
|
2118
|
+
if (textAlign) {
|
|
2119
|
+
declarations.push({ property: 'text-align', value: textAlign[1] === 'start' ? 'left' : textAlign[1] === 'end' ? 'right' : 'center' });
|
|
2120
|
+
}
|
|
2121
|
+
if (base === 'text-uppercase')
|
|
2122
|
+
declarations.push({ property: 'text-transform', value: 'uppercase' });
|
|
2123
|
+
if (base === 'text-lowercase')
|
|
2124
|
+
declarations.push({ property: 'text-transform', value: 'lowercase' });
|
|
2125
|
+
if (base === 'text-capitalize')
|
|
2126
|
+
declarations.push({ property: 'text-transform', value: 'capitalize' });
|
|
2127
|
+
if (base === 'lead') {
|
|
2128
|
+
declarations.push({ property: 'font-size', value: '1.25rem' }, { property: 'font-weight', value: '300' });
|
|
2129
|
+
}
|
|
2130
|
+
if (base === 'small')
|
|
2131
|
+
declarations.push({ property: 'font-size', value: '0.875em' });
|
|
2132
|
+
const lhMap = { '1': '1', sm: '1.25', base: '1.5', lg: '2' };
|
|
2133
|
+
const lhMatch = base.match(/^lh-(1|sm|base|lg)$/);
|
|
2134
|
+
if (lhMatch)
|
|
2135
|
+
declarations.push({ property: 'line-height', value: lhMap[lhMatch[1]] });
|
|
2136
|
+
return declarations;
|
|
2137
|
+
}
|
|
2138
|
+
function bootstrapColorDeclarations(base) {
|
|
2139
|
+
const declarations = [];
|
|
2140
|
+
const bgMatch = base.match(/^bg-(primary|secondary|success|danger|warning|info|light|dark|body|white|transparent)$/);
|
|
2141
|
+
if (bgMatch) {
|
|
2142
|
+
declarations.push({
|
|
2143
|
+
property: 'background-color',
|
|
2144
|
+
value: bgMatch[1] === 'transparent' ? 'transparent' : bootstrapThemeColors[bgMatch[1]],
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
const textMatch = base.match(/^text-(primary|secondary|success|danger|warning|info|light|dark|body|muted|white|black)$/);
|
|
2148
|
+
if (textMatch)
|
|
2149
|
+
declarations.push({ property: 'color', value: bootstrapThemeColors[textMatch[1]] });
|
|
2150
|
+
return declarations;
|
|
2151
|
+
}
|
|
2152
|
+
function bootstrapBorderDeclarations(base) {
|
|
2153
|
+
const declarations = [];
|
|
2154
|
+
if (base === 'border')
|
|
2155
|
+
declarations.push({ property: 'border', value: 'var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)' });
|
|
2156
|
+
if (base === 'border-0')
|
|
2157
|
+
declarations.push({ property: 'border', value: '0' });
|
|
2158
|
+
if (/^border-[1-5]$/.test(base)) {
|
|
2159
|
+
declarations.push({ property: 'border-width', value: `${base.replace('border-', '')}px` });
|
|
2160
|
+
}
|
|
2161
|
+
const borderColor = base.match(/^border-(primary|secondary|success|danger|warning|info|light|dark|white)$/);
|
|
2162
|
+
if (borderColor)
|
|
2163
|
+
declarations.push({ property: 'border-color', value: bootstrapThemeColors[borderColor[1]] });
|
|
2164
|
+
if (base === 'rounded')
|
|
2165
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
|
|
2166
|
+
if (base === 'rounded-0')
|
|
2167
|
+
declarations.push({ property: 'border-radius', value: '0' });
|
|
2168
|
+
if (base === 'rounded-1')
|
|
2169
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-sm)' });
|
|
2170
|
+
if (base === 'rounded-2')
|
|
2171
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius)' });
|
|
2172
|
+
if (base === 'rounded-3')
|
|
2173
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-lg)' });
|
|
2174
|
+
if (base === 'rounded-4')
|
|
2175
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xl)' });
|
|
2176
|
+
if (base === 'rounded-5')
|
|
2177
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-xxl)' });
|
|
2178
|
+
if (base === 'rounded-pill')
|
|
2179
|
+
declarations.push({ property: 'border-radius', value: 'var(--bs-border-radius-pill)' });
|
|
2180
|
+
if (base === 'rounded-circle')
|
|
2181
|
+
declarations.push({ property: 'border-radius', value: '50%' });
|
|
2182
|
+
if (base === 'shadow')
|
|
2183
|
+
declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow)' });
|
|
2184
|
+
if (base === 'shadow-sm')
|
|
2185
|
+
declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-sm)' });
|
|
2186
|
+
if (base === 'shadow-lg')
|
|
2187
|
+
declarations.push({ property: 'box-shadow', value: 'var(--bs-box-shadow-lg)' });
|
|
2188
|
+
if (base === 'shadow-none')
|
|
2189
|
+
declarations.push({ property: 'box-shadow', value: 'none' });
|
|
2190
|
+
return declarations;
|
|
2191
|
+
}
|
|
2192
|
+
function bootstrapGridDeclarations(base) {
|
|
2193
|
+
if (base === 'col') {
|
|
2194
|
+
return [
|
|
2195
|
+
{ property: 'flex', value: '1 0 0%' },
|
|
2196
|
+
{ property: 'min-width', value: '0' },
|
|
2197
|
+
];
|
|
2198
|
+
}
|
|
2199
|
+
const colMatch = base.match(/^col-(\d{1,2})$/);
|
|
2200
|
+
if (colMatch) {
|
|
2201
|
+
const columns = Math.min(12, Math.max(1, Number(colMatch[1])));
|
|
2202
|
+
const width = `${(columns / 12) * 100}%`;
|
|
2203
|
+
return [
|
|
2204
|
+
{ property: 'flex', value: '0 0 auto' },
|
|
2205
|
+
{ property: 'width', value: width },
|
|
2206
|
+
];
|
|
2207
|
+
}
|
|
2208
|
+
const gapMatch = base.match(/^gap-([0-5])$/);
|
|
2209
|
+
if (gapMatch)
|
|
2210
|
+
return [{ property: 'gap', value: bootstrapSpacingScale[gapMatch[1]] }];
|
|
2211
|
+
return [];
|
|
2212
|
+
}
|
|
2213
|
+
function categorizeResolvedStyleDeclarations(declarations) {
|
|
2214
|
+
const categories = {};
|
|
2215
|
+
for (const declaration of declarations) {
|
|
2216
|
+
const category = resolvedStyleCategory(declaration.property);
|
|
2217
|
+
categories[category] = (0, utils_1.uniqueBy)([...(categories[category] ?? []), declaration], (item) => `${item.className}:${item.property}:${item.value}:${item.source}:${item.selector ?? ''}`).slice(0, 80);
|
|
2218
|
+
}
|
|
2219
|
+
return categories;
|
|
2220
|
+
}
|
|
2221
|
+
function flattenResolvedStyleCategories(categories) {
|
|
2222
|
+
return (0, utils_1.uniqueBy)(Object.values(categories).flat(), (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`);
|
|
2223
|
+
}
|
|
2224
|
+
function createStyleSystemEvidence(classes, declarations, styleContext) {
|
|
2225
|
+
const classSystems = new Map();
|
|
2226
|
+
for (const className of classes) {
|
|
2227
|
+
const systems = new Set();
|
|
2228
|
+
const base = (0, utils_1.classBase)(className).replace(/^!/, '');
|
|
2229
|
+
if (styleContext?.classRules.has(className) ||
|
|
2230
|
+
styleContext?.classRules.has(base) ||
|
|
2231
|
+
styleContext?.classRules.has(base.replace(/^!/, ''))) {
|
|
2232
|
+
systems.add('raw-css');
|
|
2233
|
+
}
|
|
2234
|
+
if (tailwindDeclarationsForClass(className, styleContext).length || isLikelyTailwindClass(className)) {
|
|
2235
|
+
systems.add('tailwind');
|
|
2236
|
+
}
|
|
2237
|
+
if (bootstrapDeclarationsForClass(className, styleContext).length || isBootstrapClass(className)) {
|
|
2238
|
+
systems.add('bootstrap');
|
|
2239
|
+
}
|
|
2240
|
+
if (!systems.size) {
|
|
2241
|
+
systems.add('unknown');
|
|
2242
|
+
}
|
|
2243
|
+
classSystems.set(className, systems);
|
|
2244
|
+
}
|
|
2245
|
+
const observed = (0, utils_1.unique)([
|
|
2246
|
+
...Array.from(classSystems.values()).flatMap((systems) => Array.from(systems)),
|
|
2247
|
+
...declarations.map((declaration) => declaration.styleSystem),
|
|
2248
|
+
]).filter((system) => system !== 'unknown');
|
|
2249
|
+
const unresolvedClasses = classes.filter((className) => classSystems.get(className)?.has('unknown'));
|
|
2250
|
+
const classCounts = Object.fromEntries(['raw-css', 'tailwind', 'bootstrap', 'unknown'].map((system) => [
|
|
2251
|
+
system,
|
|
2252
|
+
Array.from(classSystems.values()).filter((systems) => systems.has(system)).length,
|
|
2253
|
+
]));
|
|
2254
|
+
return omitEmptyRecord({
|
|
2255
|
+
primary: observed.length === 1 ? observed[0] : observed.length > 1 ? 'mixed' : 'unknown',
|
|
2256
|
+
observed,
|
|
2257
|
+
projectDetected: styleContext?.detectedStyleSystems ?? [],
|
|
2258
|
+
classCounts,
|
|
2259
|
+
unresolvedClasses: unresolvedClasses.slice(0, 80),
|
|
2260
|
+
declarationCounts: Object.fromEntries(['raw-css', 'tailwind', 'bootstrap'].map((system) => [
|
|
2261
|
+
system,
|
|
2262
|
+
declarations.filter((declaration) => declaration.styleSystem === system).length,
|
|
2263
|
+
])),
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
function isLikelyTailwindClass(className) {
|
|
2267
|
+
const base = (0, utils_1.classBase)(className).replace(/^!/, '');
|
|
2268
|
+
return Boolean(className.includes(':') ||
|
|
2269
|
+
/\[[^\]]+\]/.test(base) ||
|
|
2270
|
+
/^(?: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));
|
|
2271
|
+
}
|
|
2272
|
+
function resolvedStyleCategory(property) {
|
|
2273
|
+
if (['color', 'background', 'background-color', 'fill', 'stroke'].includes(property)) {
|
|
2274
|
+
return 'color';
|
|
2275
|
+
}
|
|
2276
|
+
if ([
|
|
2277
|
+
'font-size',
|
|
2278
|
+
'font-weight',
|
|
2279
|
+
'line-height',
|
|
2280
|
+
'letter-spacing',
|
|
2281
|
+
'text-transform',
|
|
2282
|
+
'text-align',
|
|
2283
|
+
'font-family',
|
|
2284
|
+
].includes(property)) {
|
|
2285
|
+
return 'typography';
|
|
2286
|
+
}
|
|
2287
|
+
if (/^(?:border|outline|ring)|border-radius/.test(property)) {
|
|
2288
|
+
return 'border';
|
|
2289
|
+
}
|
|
2290
|
+
if (['box-shadow', 'opacity', 'filter', 'backdrop-filter'].includes(property)) {
|
|
2291
|
+
return 'effects';
|
|
2292
|
+
}
|
|
2293
|
+
if (['transition', 'transition-property', 'transition-duration', 'animation', 'transform'].includes(property)) {
|
|
2294
|
+
return 'motion';
|
|
2295
|
+
}
|
|
2296
|
+
if (property === 'display') {
|
|
2297
|
+
return 'display';
|
|
2298
|
+
}
|
|
2299
|
+
if (/^(?:align|justify|place)/.test(property)) {
|
|
2300
|
+
return 'alignment';
|
|
2301
|
+
}
|
|
2302
|
+
if (/^grid/.test(property)) {
|
|
2303
|
+
return 'grid';
|
|
2304
|
+
}
|
|
2305
|
+
if (/^flex/.test(property)) {
|
|
2306
|
+
return 'flex';
|
|
2307
|
+
}
|
|
2308
|
+
if (/^(?:padding|margin|gap|row-gap|column-gap)/.test(property)) {
|
|
2309
|
+
return 'spacing';
|
|
2310
|
+
}
|
|
2311
|
+
if (/^(?:width|height|min-|max-)/.test(property)) {
|
|
2312
|
+
return /^(?:min-|max-)/.test(property) ? 'minMax' : 'sizing';
|
|
2313
|
+
}
|
|
2314
|
+
if (['position', 'inset', 'top', 'right', 'bottom', 'left', 'z-index', 'object-fit'].includes(property)) {
|
|
2315
|
+
return 'position';
|
|
2316
|
+
}
|
|
2317
|
+
if (['overflow', 'overflow-x', 'overflow-y'].includes(property)) {
|
|
2318
|
+
return 'overflow';
|
|
2319
|
+
}
|
|
2320
|
+
if (['white-space', 'overflow-wrap', 'text-overflow', '-webkit-line-clamp'].includes(property)) {
|
|
2321
|
+
return 'textFlow';
|
|
2322
|
+
}
|
|
2323
|
+
return 'layout';
|
|
2324
|
+
}
|
|
2325
|
+
function mergeResolvedStyleCategories(current, next) {
|
|
2326
|
+
const merged = { ...current };
|
|
2327
|
+
for (const [category, declarations] of Object.entries(next)) {
|
|
2328
|
+
const existing = Array.isArray(merged[category])
|
|
2329
|
+
? merged[category]
|
|
2330
|
+
: [];
|
|
2331
|
+
merged[category] = (0, utils_1.uniqueBy)([...existing, ...declarations], (declaration) => `${declaration.className}:${declaration.property}:${declaration.value}:${declaration.source}:${declaration.selector ?? ''}`).slice(0, 80);
|
|
2332
|
+
}
|
|
2333
|
+
return omitEmptyRecord(merged);
|
|
2334
|
+
}
|
|
2335
|
+
function classBucketRecord(value) {
|
|
2336
|
+
const buckets = {};
|
|
2337
|
+
for (const [key, item] of Object.entries(value)) {
|
|
2338
|
+
if (Array.isArray(item) && item.every((value) => typeof value === 'string')) {
|
|
2339
|
+
buckets[key] = item;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
return buckets;
|
|
2343
|
+
}
|
|
2344
|
+
function resolvedStyleRecord(value) {
|
|
2345
|
+
return isRecordValue(value) ? value : {};
|
|
2346
|
+
}
|
|
2347
|
+
function createUiContract(input) {
|
|
2348
|
+
const defaultClasses = (0, utils_1.unique)(input.defaultClasses?.length ? input.defaultClasses : input.classes);
|
|
2349
|
+
const conditionalClasses = (0, utils_1.unique)(input.conditionalClasses ?? []);
|
|
2350
|
+
const decorativeAccentClasses = (0, utils_1.unique)(input.decorativeAccentClasses ?? []);
|
|
2351
|
+
const allClasses = (0, utils_1.unique)([...defaultClasses, ...conditionalClasses, ...decorativeAccentClasses]);
|
|
2352
|
+
const grouped = input.classGroups ?? (0, extractors_1.groupTailwindClasses)(allClasses);
|
|
2353
|
+
const resolvedStyles = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations(allClasses, input.styleContext));
|
|
2354
|
+
const styleSystemEvidence = createStyleSystemEvidence(allClasses, flattenResolvedStyleCategories(resolvedStyles), input.styleContext);
|
|
2355
|
+
const responsiveVariants = createUiResponsiveVariants(allClasses, input.styleContext);
|
|
2356
|
+
const interactionVariants = createUiInteractionVariants(allClasses, input.styleContext);
|
|
2357
|
+
return {
|
|
2358
|
+
identity: omitEmptyRecord({
|
|
2359
|
+
kind: input.kind,
|
|
2360
|
+
tag: input.tag,
|
|
2361
|
+
layoutRole: input.layoutRole,
|
|
2362
|
+
label: input.label,
|
|
2363
|
+
line: input.line,
|
|
2364
|
+
variants: input.variants,
|
|
2365
|
+
props: input.props,
|
|
2366
|
+
styleHelper: input.styleHelper,
|
|
2367
|
+
styleHelperVariants: input.styleHelperVariants,
|
|
2368
|
+
}),
|
|
2369
|
+
semantics: omitEmptyRecord({
|
|
2370
|
+
kind: input.kind,
|
|
2371
|
+
role: input.semanticRole ?? input.layoutRole,
|
|
2372
|
+
semanticRole: input.semanticRole,
|
|
2373
|
+
semanticProfile: input.semanticProfile,
|
|
2374
|
+
roleSignature: input.roleSignature,
|
|
2375
|
+
}),
|
|
2376
|
+
visual: omitEmptyRecord({
|
|
2377
|
+
color: grouped.color ?? [],
|
|
2378
|
+
typography: grouped.typography ?? [],
|
|
2379
|
+
border: grouped.border ?? [],
|
|
2380
|
+
effects: grouped.effects ?? [],
|
|
2381
|
+
motion: grouped.motion ?? [],
|
|
2382
|
+
decorativeAccentClasses,
|
|
2383
|
+
resolvedStyles: omitEmptyRecord({
|
|
2384
|
+
color: resolvedStyles.color,
|
|
2385
|
+
typography: resolvedStyles.typography,
|
|
2386
|
+
border: resolvedStyles.border,
|
|
2387
|
+
effects: resolvedStyles.effects,
|
|
2388
|
+
motion: resolvedStyles.motion,
|
|
2389
|
+
}),
|
|
2390
|
+
}),
|
|
2391
|
+
layout: omitEmptyRecord({
|
|
2392
|
+
display: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|grid|inline-grid|block|inline|inline-block|hidden|contents|table)$/.test((0, utils_1.classBase)(className))),
|
|
2393
|
+
alignment: filterUiContractClasses(allClasses, (className) => /^(?:items-|justify-|content-|self-|place-)/.test((0, utils_1.classBase)(className))),
|
|
2394
|
+
grid: filterUiContractClasses(allClasses, (className) => /^(?:grid|grid-cols-|col-|row-)/.test((0, utils_1.classBase)(className))),
|
|
2395
|
+
flex: filterUiContractClasses(allClasses, (className) => /^(?:flex|inline-flex|flex-|basis-|grow|shrink)/.test((0, utils_1.classBase)(className))),
|
|
2396
|
+
spacing: grouped.spacing ?? [],
|
|
2397
|
+
sizing: grouped.sizing ?? [],
|
|
2398
|
+
position: grouped.position ?? [],
|
|
2399
|
+
layout: grouped.layout ?? [],
|
|
2400
|
+
other: grouped.other ?? [],
|
|
2401
|
+
resolvedStyles: omitEmptyRecord({
|
|
2402
|
+
display: resolvedStyles.display,
|
|
2403
|
+
alignment: resolvedStyles.alignment,
|
|
2404
|
+
grid: resolvedStyles.grid,
|
|
2405
|
+
flex: resolvedStyles.flex,
|
|
2406
|
+
spacing: resolvedStyles.spacing,
|
|
2407
|
+
sizing: resolvedStyles.sizing,
|
|
2408
|
+
position: resolvedStyles.position,
|
|
2409
|
+
layout: resolvedStyles.layout,
|
|
2410
|
+
}),
|
|
2411
|
+
}),
|
|
2412
|
+
responsiveVariants,
|
|
2413
|
+
interactionVariants,
|
|
2414
|
+
safetyRules: omitEmptyRecord({
|
|
2415
|
+
layoutSafety: input.layoutSafety,
|
|
2416
|
+
scaleProfile: input.scaleProfile,
|
|
2417
|
+
roleConstraints: input.roleSignature
|
|
2418
|
+
? omitEmptyRecord({
|
|
2419
|
+
preserveRole: input.roleSignature.role,
|
|
2420
|
+
preserveRoleGroup: input.roleSignature.roleGroup,
|
|
2421
|
+
preserveScale: input.roleSignature.scale,
|
|
2422
|
+
preserveDensity: input.roleSignature.density,
|
|
2423
|
+
preserveSurface: input.roleSignature.surface,
|
|
2424
|
+
preserveLayout: input.roleSignature.layout,
|
|
2425
|
+
exactClassFacts: input.roleSignature.exactClassFacts,
|
|
2426
|
+
flags: input.roleSignature.flags,
|
|
2427
|
+
})
|
|
2428
|
+
: undefined,
|
|
2429
|
+
resolvedStyles: omitEmptyRecord({
|
|
2430
|
+
textFlow: resolvedStyles.textFlow,
|
|
2431
|
+
overflow: resolvedStyles.overflow,
|
|
2432
|
+
minMax: resolvedStyles.minMax,
|
|
2433
|
+
}),
|
|
2434
|
+
}),
|
|
2435
|
+
sourceEvidence: omitEmptyRecord({
|
|
2436
|
+
extractor: 'ui.contract',
|
|
2437
|
+
path: input.path,
|
|
2438
|
+
line: input.line,
|
|
2439
|
+
sourceHash: input.sourceHash,
|
|
2440
|
+
confidence: input.confidence,
|
|
2441
|
+
evidence: input.evidence,
|
|
2442
|
+
classSource: input.classSource,
|
|
2443
|
+
styleSystems: styleSystemEvidence,
|
|
2444
|
+
}),
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
function createUiContractForElement(element, file, confidence, styleContext) {
|
|
2448
|
+
return createUiContract({
|
|
2449
|
+
kind: element.kind,
|
|
2450
|
+
tag: element.originalTag,
|
|
2451
|
+
layoutRole: element.layoutRole,
|
|
2452
|
+
label: element.label,
|
|
2453
|
+
line: element.line,
|
|
2454
|
+
path: file?.relPath,
|
|
2455
|
+
sourceHash: file?.sourceHash,
|
|
2456
|
+
confidence,
|
|
2457
|
+
evidence: element.evidence,
|
|
2458
|
+
classSource: element.classSource,
|
|
2459
|
+
classes: element.classes,
|
|
2460
|
+
defaultClasses: element.defaultClasses,
|
|
2461
|
+
conditionalClasses: element.conditionalClasses,
|
|
2462
|
+
decorativeAccentClasses: element.decorativeAccentClasses,
|
|
2463
|
+
classGroups: element.classGroups,
|
|
2464
|
+
layoutSafety: element.layoutSafety,
|
|
2465
|
+
responsiveProfile: element.responsiveProfile,
|
|
2466
|
+
scaleProfile: element.scaleProfile,
|
|
2467
|
+
semanticProfile: element.semanticProfile,
|
|
2468
|
+
roleSignature: element.roleSignature,
|
|
2469
|
+
variants: element.variants,
|
|
2470
|
+
props: element.props,
|
|
2471
|
+
styleHelper: element.styleHelper,
|
|
2472
|
+
styleHelperVariants: element.styleHelperVariants,
|
|
2473
|
+
styleContext,
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
function createUiContractForChild(child, styleContext) {
|
|
2477
|
+
return createUiContract({
|
|
2478
|
+
kind: child.kind,
|
|
2479
|
+
tag: child.originalTag,
|
|
2480
|
+
layoutRole: child.layoutRole,
|
|
2481
|
+
semanticRole: child.semanticRole,
|
|
2482
|
+
label: child.label,
|
|
2483
|
+
classes: child.classes,
|
|
2484
|
+
defaultClasses: child.defaultClasses,
|
|
2485
|
+
conditionalClasses: child.conditionalClasses,
|
|
2486
|
+
layoutSafety: child.layoutSafety,
|
|
2487
|
+
responsiveProfile: child.responsiveProfile,
|
|
2488
|
+
scaleProfile: child.scaleProfile,
|
|
2489
|
+
semanticProfile: child.semanticProfile,
|
|
2490
|
+
roleSignature: child.roleSignature,
|
|
2491
|
+
variants: child.variants,
|
|
2492
|
+
props: child.props,
|
|
2493
|
+
styleHelper: child.styleHelper,
|
|
2494
|
+
styleHelperVariants: child.styleHelperVariants,
|
|
2495
|
+
styleContext,
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
function createUiPatternContract(input) {
|
|
2499
|
+
const contract = createUiContract(input);
|
|
2500
|
+
return {
|
|
2501
|
+
...contract,
|
|
2502
|
+
identity: omitEmptyRecord({
|
|
2503
|
+
...contract.identity,
|
|
2504
|
+
name: input.name,
|
|
2505
|
+
count: input.count,
|
|
2506
|
+
}),
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
function createUiRelationships(input) {
|
|
2510
|
+
return {
|
|
2511
|
+
...(input.parentContext ? { parentContext: input.parentContext } : {}),
|
|
2512
|
+
...(input.childStructureContract
|
|
2513
|
+
? { childStructureContract: input.childStructureContract }
|
|
2514
|
+
: {}),
|
|
2515
|
+
...(input.compoundStructure ? { compoundStructure: input.compoundStructure } : {}),
|
|
2516
|
+
graphKeys: input.graphKeys ?? [],
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
function createUiResponsiveVariants(classes, styleContext) {
|
|
2520
|
+
const variants = {};
|
|
2521
|
+
for (const className of classes) {
|
|
2522
|
+
const responsivePrefixes = responsivePrefixesForClass(className);
|
|
2523
|
+
for (const responsivePrefix of responsivePrefixes) {
|
|
2524
|
+
variants[responsivePrefix] = mergeUiContractClassBuckets(classBucketRecord(variants[responsivePrefix] ?? {}), uiContractClassBucket(className));
|
|
2525
|
+
const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
|
|
2526
|
+
variants[responsivePrefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[responsivePrefix].resolvedStyles), resolved);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
return variants;
|
|
2530
|
+
}
|
|
2531
|
+
function createUiInteractionVariants(classes, styleContext) {
|
|
2532
|
+
const variants = {};
|
|
2533
|
+
for (const className of classes) {
|
|
2534
|
+
const prefixes = className.split(':').slice(0, -1);
|
|
2535
|
+
const statePrefixes = prefixes.filter(isInteractionPrefix);
|
|
2536
|
+
for (const prefix of statePrefixes) {
|
|
2537
|
+
variants[prefix] = mergeUiContractClassBuckets(classBucketRecord(variants[prefix] ?? {}), uiContractClassBucket(className));
|
|
2538
|
+
const resolved = categorizeResolvedStyleDeclarations(resolveUiClassDeclarations([className], styleContext));
|
|
2539
|
+
variants[prefix].resolvedStyles = mergeResolvedStyleCategories(resolvedStyleRecord(variants[prefix].resolvedStyles), resolved);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
return variants;
|
|
2543
|
+
}
|
|
2544
|
+
function uiContractClassBucket(className) {
|
|
2545
|
+
const base = (0, utils_1.classBase)(className);
|
|
2546
|
+
if ((0, extractors_1.isLayoutClass)(className)) {
|
|
2547
|
+
return { layout: [className] };
|
|
2548
|
+
}
|
|
2549
|
+
if ((0, extractors_1.isSpacingClass)(className)) {
|
|
2550
|
+
return { spacing: [className] };
|
|
2551
|
+
}
|
|
2552
|
+
if ((0, extractors_1.isSizingClass)(className)) {
|
|
2553
|
+
return { sizing: [className] };
|
|
2554
|
+
}
|
|
2555
|
+
if ((0, extractors_1.isTypographyClass)(className)) {
|
|
2556
|
+
return { typography: [className] };
|
|
2557
|
+
}
|
|
2558
|
+
if (/^(?:bg-|from-|via-|to-|text-|decoration-|accent-|caret-|fill-|stroke-)/.test(base)) {
|
|
2559
|
+
return { color: [className] };
|
|
2560
|
+
}
|
|
2561
|
+
if (/^(?:rounded|border|divide-|outline|ring)/.test(base)) {
|
|
2562
|
+
return { border: [className] };
|
|
2563
|
+
}
|
|
2564
|
+
if (/^(?:shadow|opacity|blur|backdrop|mix-|bg-blend|drop-shadow|filter)/.test(base)) {
|
|
2565
|
+
return { effects: [className] };
|
|
2566
|
+
}
|
|
2567
|
+
if (/^(?:absolute|relative|fixed|sticky|z-|overflow-|object-)/.test(base)) {
|
|
2568
|
+
return { position: [className] };
|
|
2569
|
+
}
|
|
2570
|
+
if (/^(?:transition|duration|ease|delay|animate-|transform|scale-|rotate|translate-)/.test(base)) {
|
|
2571
|
+
return { motion: [className] };
|
|
2572
|
+
}
|
|
2573
|
+
return { other: [className] };
|
|
2574
|
+
}
|
|
2575
|
+
function mergeUiContractClassBuckets(current, next) {
|
|
2576
|
+
const merged = { ...current };
|
|
2577
|
+
for (const [bucket, classes] of Object.entries(next)) {
|
|
2578
|
+
merged[bucket] = (0, utils_1.unique)([...(merged[bucket] ?? []), ...classes]).slice(0, 120);
|
|
2579
|
+
}
|
|
2580
|
+
return merged;
|
|
2581
|
+
}
|
|
2582
|
+
function filterUiContractClasses(classes, predicate) {
|
|
2583
|
+
return (0, utils_1.unique)(classes.filter(predicate)).slice(0, 120);
|
|
2584
|
+
}
|
|
2585
|
+
function isResponsivePrefix(prefix) {
|
|
2586
|
+
return /^(?:sm|md|lg|xl|2xl|min-|max-)/.test(prefix);
|
|
2587
|
+
}
|
|
2588
|
+
function responsivePrefixesForClass(className) {
|
|
2589
|
+
const prefixes = className.split(':').slice(0, -1).filter(isResponsivePrefix);
|
|
2590
|
+
const bootstrapPrefixes = bootstrapResponsivePrefixes(className);
|
|
2591
|
+
return (0, utils_1.unique)([...prefixes, ...bootstrapPrefixes]);
|
|
2592
|
+
}
|
|
2593
|
+
function isInteractionPrefix(prefix) {
|
|
2594
|
+
return /^(?:hover|focus|focus-visible|focus-within|active|disabled|visited|checked|selected|open|dark|group|group-hover|peer|peer-hover|aria|data)/.test(prefix);
|
|
2595
|
+
}
|
|
2596
|
+
function omitEmptyRecord(value) {
|
|
2597
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => {
|
|
2598
|
+
if (item === undefined || item === null) {
|
|
2599
|
+
return false;
|
|
2600
|
+
}
|
|
2601
|
+
if (Array.isArray(item)) {
|
|
2602
|
+
return item.length > 0;
|
|
2603
|
+
}
|
|
2604
|
+
if (typeof item === 'object') {
|
|
2605
|
+
return Object.keys(item).length > 0;
|
|
2606
|
+
}
|
|
2607
|
+
return true;
|
|
2608
|
+
}));
|
|
2609
|
+
}
|
|
2610
|
+
function mergeLayoutSafetyProfiles(items) {
|
|
2611
|
+
const profiles = items.map((item) => item.layoutSafety).filter(isLayoutSafetyProfile);
|
|
2612
|
+
if (!profiles.length) {
|
|
2613
|
+
return undefined;
|
|
2614
|
+
}
|
|
2615
|
+
const merged = {
|
|
2616
|
+
wrapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.wrapClasses)).slice(0, 80),
|
|
2617
|
+
overflowClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.overflowClasses)).slice(0, 80),
|
|
2618
|
+
minSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.minSizeClasses)).slice(0, 80),
|
|
2619
|
+
maxSizeClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxSizeClasses)).slice(0, 80),
|
|
2620
|
+
fitClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.fitClasses)).slice(0, 80),
|
|
2621
|
+
flexBehaviorClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.flexBehaviorClasses)).slice(0, 80),
|
|
2622
|
+
layoutSafetyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutSafetyClasses)).slice(0, 120),
|
|
2623
|
+
};
|
|
2624
|
+
return merged.layoutSafetyClasses.length ? merged : undefined;
|
|
2625
|
+
}
|
|
2626
|
+
function mergeResponsiveProfiles(items) {
|
|
2627
|
+
const profiles = items.map((item) => item.responsiveProfile).filter(isResponsiveProfile);
|
|
2628
|
+
if (!profiles.length) {
|
|
2629
|
+
return undefined;
|
|
2630
|
+
}
|
|
2631
|
+
const breakpoints = {};
|
|
2632
|
+
for (const profile of profiles) {
|
|
2633
|
+
for (const [breakpoint, classes] of Object.entries(profile.breakpoints)) {
|
|
2634
|
+
breakpoints[breakpoint] = (0, utils_1.unique)([...(breakpoints[breakpoint] ?? []), ...classes]).slice(0, 80);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
const merged = {
|
|
2638
|
+
breakpoints,
|
|
2639
|
+
layoutClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.layoutClasses)).slice(0, 120),
|
|
2640
|
+
typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
|
|
2641
|
+
spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
|
|
2642
|
+
sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
|
|
2643
|
+
visibilityClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.visibilityClasses)).slice(0, 120),
|
|
2644
|
+
stateClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.stateClasses)).slice(0, 120),
|
|
2645
|
+
};
|
|
2646
|
+
return Object.keys(merged.breakpoints).length ? merged : undefined;
|
|
2647
|
+
}
|
|
2648
|
+
function mergeScaleProfiles(items) {
|
|
2649
|
+
const profiles = items.map((item) => item.scaleProfile).filter(isScaleProfile);
|
|
2650
|
+
if (!profiles.length) {
|
|
2651
|
+
return undefined;
|
|
2652
|
+
}
|
|
2653
|
+
const merged = {
|
|
2654
|
+
typographyClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.typographyClasses)).slice(0, 120),
|
|
2655
|
+
spacingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.spacingClasses)).slice(0, 120),
|
|
2656
|
+
sizingClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.sizingClasses)).slice(0, 120),
|
|
2657
|
+
gapClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.gapClasses)).slice(0, 80),
|
|
2658
|
+
maxWidthClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.maxWidthClasses)).slice(0, 80),
|
|
2659
|
+
lineHeightClasses: (0, utils_1.unique)(profiles.flatMap((profile) => profile.lineHeightClasses)).slice(0, 80),
|
|
2660
|
+
};
|
|
2661
|
+
return Object.values(merged).some((classes) => classes.length) ? merged : undefined;
|
|
2662
|
+
}
|
|
2663
|
+
function mergeSemanticProfiles(items) {
|
|
2664
|
+
const profiles = items.map((item) => item.semanticProfile).filter(isSemanticProfile);
|
|
2665
|
+
if (!profiles.length) {
|
|
2666
|
+
return undefined;
|
|
659
2667
|
}
|
|
2668
|
+
return {
|
|
2669
|
+
roles: (0, utils_1.unique)(profiles.flatMap((profile) => profile.roles)).slice(0, 40),
|
|
2670
|
+
isCompound: profiles.some((profile) => profile.isCompound),
|
|
2671
|
+
hasIcon: profiles.some((profile) => profile.hasIcon),
|
|
2672
|
+
hasDivider: profiles.some((profile) => profile.hasDivider),
|
|
2673
|
+
hasImage: profiles.some((profile) => profile.hasImage),
|
|
2674
|
+
hasMultipleTextStyles: profiles.some((profile) => profile.hasMultipleTextStyles),
|
|
2675
|
+
textStyleCount: Math.max(...profiles.map((profile) => profile.textStyleCount)),
|
|
2676
|
+
childCount: Math.max(...profiles.map((profile) => profile.childCount)),
|
|
2677
|
+
};
|
|
660
2678
|
}
|
|
661
|
-
function
|
|
662
|
-
return (
|
|
2679
|
+
function summarizePatternLayoutSafety(elements) {
|
|
2680
|
+
return mergeLayoutSafetyProfiles([
|
|
2681
|
+
...elements,
|
|
2682
|
+
...elements.flatMap((element) => element.childSummary ?? []),
|
|
2683
|
+
]);
|
|
663
2684
|
}
|
|
664
|
-
function
|
|
665
|
-
return (
|
|
2685
|
+
function summarizePatternResponsiveProfile(elements) {
|
|
2686
|
+
return mergeResponsiveProfiles([
|
|
2687
|
+
...elements,
|
|
2688
|
+
...elements.flatMap((element) => element.childSummary ?? []),
|
|
2689
|
+
]);
|
|
666
2690
|
}
|
|
667
|
-
function
|
|
2691
|
+
function summarizePatternScaleProfile(elements) {
|
|
2692
|
+
return mergeScaleProfiles([
|
|
2693
|
+
...elements,
|
|
2694
|
+
...elements.flatMap((element) => element.childSummary ?? []),
|
|
2695
|
+
]);
|
|
2696
|
+
}
|
|
2697
|
+
function summarizePatternSemanticProfile(elements) {
|
|
2698
|
+
return mergeSemanticProfiles(elements);
|
|
2699
|
+
}
|
|
2700
|
+
function summarizePatternRoleSignatures(elements) {
|
|
2701
|
+
return (0, utils_1.uniqueBy)(elements
|
|
2702
|
+
.map((element) => element.roleSignature)
|
|
2703
|
+
.filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
|
|
2704
|
+
}
|
|
2705
|
+
function summarizeParentContexts(elements) {
|
|
2706
|
+
const elementsWithParent = elements.filter((element) => element.parentClasses?.length);
|
|
2707
|
+
const grouped = (0, utils_1.groupBy)(elementsWithParent, (element) => [
|
|
2708
|
+
element.parentKind ?? 'unknown-parent',
|
|
2709
|
+
element.parentTag ?? 'unknown-tag',
|
|
2710
|
+
JSON.stringify(element.parentClassGroups ?? (0, extractors_1.groupTailwindClasses)(element.parentClasses ?? [])),
|
|
2711
|
+
].join(':'));
|
|
2712
|
+
return Array.from(grouped.values())
|
|
2713
|
+
.map((group) => {
|
|
2714
|
+
const first = group[0];
|
|
2715
|
+
const parentClasses = (0, utils_1.unique)(group.flatMap((element) => element.parentClasses ?? [])).slice(0, 80);
|
|
2716
|
+
return {
|
|
2717
|
+
parentKind: first?.parentKind,
|
|
2718
|
+
parentTag: first?.parentTag,
|
|
2719
|
+
count: group.length,
|
|
2720
|
+
parentClasses,
|
|
2721
|
+
parentClassGroups: (0, extractors_1.mergeClassGroups)(group
|
|
2722
|
+
.map((element) => element.parentClassGroups)
|
|
2723
|
+
.filter((value) => Boolean(value))),
|
|
2724
|
+
childKinds: (0, utils_1.unique)(group.map((element) => element.kind)).slice(0, 24),
|
|
2725
|
+
layoutRoles: (0, utils_1.unique)(group
|
|
2726
|
+
.map((element) => element.layoutRole)
|
|
2727
|
+
.filter((value) => Boolean(value))).slice(0, 24),
|
|
2728
|
+
};
|
|
2729
|
+
})
|
|
2730
|
+
.sort((a, b) => Number(b.count) - Number(a.count))
|
|
2731
|
+
.slice(0, 12);
|
|
2732
|
+
}
|
|
2733
|
+
function summarizeElementChildContract(children, styleContext) {
|
|
2734
|
+
if (!children.length) {
|
|
2735
|
+
return undefined;
|
|
2736
|
+
}
|
|
668
2737
|
return {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
2738
|
+
directChildCount: children.length,
|
|
2739
|
+
childOrder: children.slice(0, 12).map((child) => ({
|
|
2740
|
+
kind: child.kind,
|
|
2741
|
+
tag: child.originalTag,
|
|
2742
|
+
layoutRole: child.layoutRole,
|
|
2743
|
+
semanticRole: child.semanticRole,
|
|
2744
|
+
roleSignature: child.roleSignature,
|
|
2745
|
+
labelHint: child.label ? labelSignature(child.label) : undefined,
|
|
2746
|
+
label: child.label,
|
|
2747
|
+
uiContract: createUiContractForChild(child, styleContext),
|
|
2748
|
+
defaultClasses: (child.defaultClasses?.length ? child.defaultClasses : child.classes).slice(0, 48),
|
|
2749
|
+
layoutSafety: child.layoutSafety,
|
|
2750
|
+
scaleProfile: child.scaleProfile,
|
|
2751
|
+
classGroups: (0, extractors_1.groupTailwindClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes),
|
|
2752
|
+
})),
|
|
2753
|
+
childKinds: (0, utils_1.unique)(children.map((child) => child.kind)).slice(0, 24),
|
|
2754
|
+
semanticRoles: (0, utils_1.unique)(children
|
|
2755
|
+
.map((child) => child.semanticRole)
|
|
2756
|
+
.filter((value) => Boolean(value))).slice(0, 24),
|
|
2757
|
+
layoutRoles: (0, utils_1.unique)(children.map((child) => child.layoutRole).filter((value) => Boolean(value))).slice(0, 24),
|
|
674
2758
|
};
|
|
675
2759
|
}
|
|
676
|
-
function
|
|
2760
|
+
function summarizePatternChildStructure(elements, styleContext) {
|
|
2761
|
+
const childSummaries = elements
|
|
2762
|
+
.map((element) => element.childSummary ?? [])
|
|
2763
|
+
.filter((children) => children.length);
|
|
2764
|
+
if (!childSummaries.length) {
|
|
2765
|
+
return undefined;
|
|
2766
|
+
}
|
|
2767
|
+
const childCounts = childSummaries.map((children) => children.length);
|
|
2768
|
+
const childOrderExamples = (0, utils_1.uniqueBy)(childSummaries
|
|
2769
|
+
.map((children) => summarizeElementChildContract(children, styleContext))
|
|
2770
|
+
.filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
|
|
677
2771
|
return {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
source: sourceInfo(extractor, 'high', [`path:${relPath}`], file),
|
|
2772
|
+
preserveDirectChildRelationships: true,
|
|
2773
|
+
directChildCountRange: {
|
|
2774
|
+
min: Math.min(...childCounts),
|
|
2775
|
+
max: Math.max(...childCounts),
|
|
2776
|
+
},
|
|
2777
|
+
childKinds: (0, utils_1.unique)(childSummaries.flatMap((children) => children.map((child) => child.kind))).slice(0, 32),
|
|
2778
|
+
childOrderExamples,
|
|
686
2779
|
};
|
|
687
2780
|
}
|
|
688
|
-
function
|
|
2781
|
+
function summarizeCompoundStructure(elements, styleContext) {
|
|
2782
|
+
const compoundElements = elements.filter((element) => element.semanticProfile?.isCompound);
|
|
2783
|
+
if (!compoundElements.length) {
|
|
2784
|
+
return undefined;
|
|
2785
|
+
}
|
|
2786
|
+
const childCounts = compoundElements.map((element) => element.childSummary?.length ?? 0);
|
|
2787
|
+
const childOrderExamples = (0, utils_1.uniqueBy)(compoundElements
|
|
2788
|
+
.map((element) => summarizeElementChildContract(element.childSummary ?? [], styleContext))
|
|
2789
|
+
.filter((summary) => Boolean(summary)), (summary) => JSON.stringify(summary)).slice(0, 8);
|
|
2790
|
+
const semanticProfile = summarizePatternSemanticProfile(compoundElements);
|
|
689
2791
|
return {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
2792
|
+
preserveCompoundChildren: true,
|
|
2793
|
+
preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
|
|
2794
|
+
preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
|
|
2795
|
+
directChildCountRange: {
|
|
2796
|
+
min: Math.min(...childCounts),
|
|
2797
|
+
max: Math.max(...childCounts),
|
|
2798
|
+
},
|
|
2799
|
+
semanticRoles: semanticProfile?.roles ?? [],
|
|
2800
|
+
childOrderExamples,
|
|
695
2801
|
};
|
|
696
2802
|
}
|
|
697
|
-
function
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
2803
|
+
function createUiGenerationConstraints(kind, elements, decorativeAccentClasses) {
|
|
2804
|
+
const layoutSafety = summarizePatternLayoutSafety(elements);
|
|
2805
|
+
const responsiveProfile = summarizePatternResponsiveProfile(elements);
|
|
2806
|
+
const scaleProfile = summarizePatternScaleProfile(elements);
|
|
2807
|
+
const roleSignatures = summarizePatternRoleSignatures(elements);
|
|
2808
|
+
const parentContexts = summarizeParentContexts(elements);
|
|
2809
|
+
const childStructure = summarizePatternChildStructure(elements);
|
|
2810
|
+
const compoundStructure = summarizeCompoundStructure(elements);
|
|
2811
|
+
return {
|
|
2812
|
+
preserveRootAndChildrenTogether: true,
|
|
2813
|
+
preserveParentLayoutContext: Boolean(parentContexts.length),
|
|
2814
|
+
preserveDirectChildRelationships: Boolean(childStructure),
|
|
2815
|
+
preserveCompoundChildRoles: Boolean(compoundStructure),
|
|
2816
|
+
avoidUnobservedChildren: ['card', 'form', 'nav', 'section', 'table', 'dialog'].includes(kind) ||
|
|
2817
|
+
Boolean(compoundStructure),
|
|
2818
|
+
preserveResponsiveBreakpoints: Boolean(responsiveProfile),
|
|
2819
|
+
preserveLayoutSafety: Boolean(layoutSafety),
|
|
2820
|
+
preserveScaleBounds: Boolean(scaleProfile),
|
|
2821
|
+
preserveRoleSignatures: Boolean(roleSignatures.length),
|
|
2822
|
+
avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
function createUiGenerationGuidance(kind, elements) {
|
|
2826
|
+
const guidance = [];
|
|
2827
|
+
const elementClasses = new Set(elements.flatMap((element) => [
|
|
2828
|
+
...element.classes,
|
|
2829
|
+
...(element.defaultClasses ?? []),
|
|
2830
|
+
...(element.conditionalClasses ?? []),
|
|
2831
|
+
...(element.parentClasses ?? []),
|
|
2832
|
+
]));
|
|
2833
|
+
if (kind === 'card') {
|
|
2834
|
+
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
2835
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
2836
|
+
if (kind === 'button') {
|
|
2837
|
+
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.');
|
|
2838
|
+
}
|
|
2839
|
+
if (kind === 'badge' && summarizeCompoundStructure(elements)) {
|
|
2840
|
+
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.');
|
|
2841
|
+
}
|
|
2842
|
+
if (kind === 'heading') {
|
|
2843
|
+
guidance.push('Preserve observed heading scale, max-width, line-height, and responsive typography classes. Do not upscale headings beyond the captured scaleProfile.');
|
|
2844
|
+
}
|
|
2845
|
+
if (['section', 'layout', 'form', 'nav'].includes(kind)) {
|
|
2846
|
+
guidance.push('Preserve parent grid/flex context, responsive breakpoints, and child order as layout contracts instead of recreating the section from isolated tokens.');
|
|
2847
|
+
}
|
|
2848
|
+
if (elements.some((element) => element.conditionalClasses?.length)) {
|
|
2849
|
+
guidance.push('conditionalClasses describe state or variant changes; apply them only when the matching condition, selected state, or variant is present.');
|
|
2850
|
+
}
|
|
2851
|
+
if (elementClasses.has('section-frame') || elementClasses.has('sm:section-frame')) {
|
|
2852
|
+
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.');
|
|
2853
|
+
}
|
|
2854
|
+
if (elementClasses.has('section-frame') && elementClasses.has('context-surface')) {
|
|
2855
|
+
guidance.push('When section-frame and context-surface appear together, keep the pairing so direct children remain above the parent grid overlay.');
|
|
2856
|
+
}
|
|
2857
|
+
if (elements.some((element) => (element.childSummary ?? []).some((child) => (child.defaultClasses ?? child.classes).some((className) => className === 'section-frame')))) {
|
|
2858
|
+
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.');
|
|
2859
|
+
}
|
|
2860
|
+
if (summarizePatternLayoutSafety(elements)) {
|
|
2861
|
+
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.');
|
|
2862
|
+
}
|
|
2863
|
+
if (summarizeParentContexts(elements).length) {
|
|
2864
|
+
guidance.push('parentContexts records the grid/flex surface that owns this pattern. Preserve that parent relationship when generating repeated cards or nested panels.');
|
|
2865
|
+
}
|
|
2866
|
+
if (summarizePatternResponsiveProfile(elements)) {
|
|
2867
|
+
guidance.push('responsiveProfile is part of the contract. Keep mobile, tablet, and desktop variants attached to the same elements as the source.');
|
|
2868
|
+
}
|
|
2869
|
+
if (summarizePatternRoleSignatures(elements).length) {
|
|
2870
|
+
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.');
|
|
2871
|
+
}
|
|
2872
|
+
return guidance;
|
|
2873
|
+
}
|
|
2874
|
+
function summarizeActionUsages(elements) {
|
|
2875
|
+
return elements.slice(0, 80).flatMap((element) => {
|
|
2876
|
+
const variantNames = actionVariantNamesForElement(element);
|
|
2877
|
+
return variantNames.map((variantName) => ({
|
|
2878
|
+
label: element.label,
|
|
2879
|
+
tag: element.originalTag,
|
|
2880
|
+
line: element.line,
|
|
2881
|
+
variant: variantName,
|
|
2882
|
+
size: element.variants?.size,
|
|
2883
|
+
type: element.variants?.type ?? element.type,
|
|
2884
|
+
styleHelper: element.styleHelper,
|
|
2885
|
+
parentKind: element.parentKind,
|
|
2886
|
+
parentLayoutRole: element.parentKind ? element.layoutRole : undefined,
|
|
2887
|
+
...inferActionVariantRole(variantName, [
|
|
2888
|
+
...(element.defaultClasses ?? []),
|
|
2889
|
+
...(element.classes ?? []),
|
|
2890
|
+
]),
|
|
2891
|
+
}));
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
function actionVariantNamesForElement(element) {
|
|
2895
|
+
const variantValue = element.variants?.variant ?? element.styleHelperVariants?.variant;
|
|
2896
|
+
if (!variantValue && /button/i.test(element.styleHelper ?? '')) {
|
|
2897
|
+
return ['default'];
|
|
718
2898
|
}
|
|
2899
|
+
if (!variantValue) {
|
|
2900
|
+
return ['custom'];
|
|
2901
|
+
}
|
|
2902
|
+
const quotedValues = Array.from(variantValue.matchAll(/["']([^"']+)["']/g)).map((match) => match[1]);
|
|
2903
|
+
if (quotedValues.length) {
|
|
2904
|
+
return (0, utils_1.unique)(quotedValues).slice(0, 6);
|
|
2905
|
+
}
|
|
2906
|
+
return (0, utils_1.unique)(variantValue
|
|
2907
|
+
.split('|')
|
|
2908
|
+
.map((value) => value.trim())
|
|
2909
|
+
.filter(Boolean)).slice(0, 6);
|
|
719
2910
|
}
|
|
720
|
-
function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
2911
|
+
function addUiPatternRules(entries, usedKeys, nodes, edges, files, styleContext) {
|
|
721
2912
|
for (const file of files) {
|
|
2913
|
+
if ((0, extractors_1.isTestFile)(file.relPath)) {
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
722
2916
|
const elementsByKind = (0, utils_1.groupBy)(file.uiElements, (element) => element.kind);
|
|
723
2917
|
for (const [kind, elements] of elementsByKind) {
|
|
724
2918
|
if (!elements.length || kind === 'unknown' || kind === 'link') {
|
|
@@ -734,6 +2928,62 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
734
2928
|
const decorativeAccentClasses = (0, extractors_1.filterClasses)(classes, extractors_1.isDecorativeAccentClass).slice(0, 40);
|
|
735
2929
|
const classGroups = (0, extractors_1.mergeClassGroups)(elements.map((element) => element.classGroups ?? {}));
|
|
736
2930
|
const variants = (0, extractors_1.mergeVariantProps)(elements.map((element) => element.variants ?? {}));
|
|
2931
|
+
const elementPatterns = summarizeUiElementPatterns(elements, styleContext);
|
|
2932
|
+
const layoutSafety = summarizePatternLayoutSafety(elements);
|
|
2933
|
+
const responsiveProfile = summarizePatternResponsiveProfile(elements);
|
|
2934
|
+
const scaleProfile = summarizePatternScaleProfile(elements);
|
|
2935
|
+
const semanticProfile = summarizePatternSemanticProfile(elements);
|
|
2936
|
+
const roleSignatures = summarizePatternRoleSignatures(elements);
|
|
2937
|
+
const parentContexts = summarizeParentContexts(elements);
|
|
2938
|
+
const childStructureContract = summarizePatternChildStructure(elements, styleContext);
|
|
2939
|
+
const compoundStructure = summarizeCompoundStructure(elements, styleContext);
|
|
2940
|
+
const generationConstraints = createUiGenerationConstraints(kind, elements, decorativeAccentClasses);
|
|
2941
|
+
const structuralRootElements = rootUiElementsForKind(elements);
|
|
2942
|
+
const rootDefaultClasses = (0, utils_1.unique)(structuralRootElements.flatMap((element) => element.defaultClasses ?? element.classes)).slice(0, 80);
|
|
2943
|
+
const recipeDefaultClasses = kind === 'card' && rootDefaultClasses.length ? rootDefaultClasses : defaultClasses;
|
|
2944
|
+
const nestedDefaultClasses = kind === 'card'
|
|
2945
|
+
? (0, utils_1.unique)(elements
|
|
2946
|
+
.filter((element) => !structuralRootElements.includes(element))
|
|
2947
|
+
.flatMap((element) => element.defaultClasses ?? element.classes)
|
|
2948
|
+
.filter((className) => !(0, extractors_1.isDecorativeAccentClass)(className))).slice(0, 80)
|
|
2949
|
+
: undefined;
|
|
2950
|
+
const actionUsages = kind === 'button' ? summarizeActionUsages(elements) : undefined;
|
|
2951
|
+
const uiContract = createUiPatternContract({
|
|
2952
|
+
name: `${(0, utils_1.titleCase)(kind)} pattern`,
|
|
2953
|
+
count: elements.length,
|
|
2954
|
+
kind,
|
|
2955
|
+
tag: structuralRootElements[0]?.originalTag,
|
|
2956
|
+
layoutRole: structuralRootElements[0]?.layoutRole,
|
|
2957
|
+
label: structuralRootElements[0]?.label,
|
|
2958
|
+
line: structuralRootElements[0]?.line,
|
|
2959
|
+
path: file.relPath,
|
|
2960
|
+
sourceHash: file.sourceHash,
|
|
2961
|
+
confidence: elements.length > 1 ? 'medium' : 'low',
|
|
2962
|
+
evidence: structuralRootElements[0]?.evidence,
|
|
2963
|
+
classSource: structuralRootElements[0]?.classSource,
|
|
2964
|
+
classes,
|
|
2965
|
+
defaultClasses: recipeDefaultClasses,
|
|
2966
|
+
conditionalClasses,
|
|
2967
|
+
decorativeAccentClasses,
|
|
2968
|
+
classGroups,
|
|
2969
|
+
layoutSafety,
|
|
2970
|
+
responsiveProfile,
|
|
2971
|
+
scaleProfile,
|
|
2972
|
+
semanticProfile,
|
|
2973
|
+
roleSignature: roleSignatures[0],
|
|
2974
|
+
variants,
|
|
2975
|
+
styleContext,
|
|
2976
|
+
});
|
|
2977
|
+
uiContract.safetyRules = omitEmptyRecord({
|
|
2978
|
+
...uiContract.safetyRules,
|
|
2979
|
+
generationConstraints,
|
|
2980
|
+
});
|
|
2981
|
+
const relationships = createUiRelationships({
|
|
2982
|
+
parentContext: parentContexts,
|
|
2983
|
+
childStructureContract,
|
|
2984
|
+
compoundStructure,
|
|
2985
|
+
graphKeys: dependencies,
|
|
2986
|
+
});
|
|
737
2987
|
addEntry(entries, usedKeys, {
|
|
738
2988
|
key: ruleKey,
|
|
739
2989
|
group: 'rule',
|
|
@@ -744,8 +2994,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
744
2994
|
kind,
|
|
745
2995
|
...Object.keys(variants),
|
|
746
2996
|
...Object.values(variants).flatMap((values) => values),
|
|
747
|
-
...
|
|
2997
|
+
...recipeDefaultClasses.slice(0, 20),
|
|
748
2998
|
...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
|
|
2999
|
+
...(layoutSafety
|
|
3000
|
+
? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 16)]
|
|
3001
|
+
: []),
|
|
3002
|
+
...(responsiveProfile ? ['responsive-contract'] : []),
|
|
3003
|
+
...(scaleProfile ? ['scale-contract'] : []),
|
|
3004
|
+
...(roleSignatures.length
|
|
3005
|
+
? [
|
|
3006
|
+
'role-signature',
|
|
3007
|
+
...roleSignatures.flatMap((signature) => [
|
|
3008
|
+
signature.role,
|
|
3009
|
+
signature.roleGroup,
|
|
3010
|
+
signature.scale,
|
|
3011
|
+
signature.density,
|
|
3012
|
+
signature.surface,
|
|
3013
|
+
signature.layout,
|
|
3014
|
+
...signature.flags,
|
|
3015
|
+
]),
|
|
3016
|
+
]
|
|
3017
|
+
: []),
|
|
3018
|
+
...(semanticProfile?.isCompound
|
|
3019
|
+
? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
|
|
3020
|
+
: []),
|
|
749
3021
|
]),
|
|
750
3022
|
dependencies,
|
|
751
3023
|
usedBy: [],
|
|
@@ -759,12 +3031,30 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
759
3031
|
concept: kind,
|
|
760
3032
|
path: file.relPath,
|
|
761
3033
|
classes,
|
|
762
|
-
defaultClasses,
|
|
3034
|
+
defaultClasses: recipeDefaultClasses,
|
|
3035
|
+
rootDefaultClasses,
|
|
3036
|
+
...(nestedDefaultClasses?.length ? { nestedDefaultClasses } : {}),
|
|
763
3037
|
conditionalClasses,
|
|
764
3038
|
decorativeAccentClasses,
|
|
765
|
-
|
|
3039
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
3040
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
3041
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
3042
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
3043
|
+
...(roleSignatures.length ? { roleSignatures } : {}),
|
|
3044
|
+
...(parentContexts.length ? { parentContexts } : {}),
|
|
3045
|
+
...(childStructureContract ? { childStructureContract } : {}),
|
|
3046
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3047
|
+
generationConstraints,
|
|
3048
|
+
uiContract,
|
|
3049
|
+
relationships,
|
|
3050
|
+
guidance: (0, utils_1.unique)([
|
|
3051
|
+
...(0, extractors_1.createUiPatternGuidance)(kind, decorativeAccentClasses),
|
|
3052
|
+
...createUiGenerationGuidance(kind, elements),
|
|
3053
|
+
]),
|
|
766
3054
|
classGroups,
|
|
767
3055
|
variants,
|
|
3056
|
+
elementPatterns,
|
|
3057
|
+
...(actionUsages ? { actionUsages } : {}),
|
|
768
3058
|
elements: elements.slice(0, 80),
|
|
769
3059
|
sourceHash: file.sourceHash,
|
|
770
3060
|
source: sourceInfo('ui.patterns', elements.length > 1 ? 'medium' : 'low', [`${kind}:${elements.length}`], file),
|
|
@@ -773,8 +3063,12 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
773
3063
|
nodes.push(toGraphNode(ruleKey, 'rule', `${kind} pattern`, file.relPath, file, 'ui.patterns', {
|
|
774
3064
|
ui: {
|
|
775
3065
|
concept: kind,
|
|
776
|
-
classes: defaultClasses,
|
|
3066
|
+
classes: rootDefaultClasses.length ? rootDefaultClasses : defaultClasses,
|
|
777
3067
|
decorativeAccentClasses,
|
|
3068
|
+
layoutSafety,
|
|
3069
|
+
responsiveProfile,
|
|
3070
|
+
scaleProfile,
|
|
3071
|
+
semanticProfile,
|
|
778
3072
|
classGroups,
|
|
779
3073
|
variants,
|
|
780
3074
|
count: elements.length,
|
|
@@ -784,8 +3078,11 @@ function addUiPatternRules(entries, usedKeys, nodes, edges, files) {
|
|
|
784
3078
|
}
|
|
785
3079
|
}
|
|
786
3080
|
}
|
|
787
|
-
function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
3081
|
+
function addUiCompositionRules(entries, usedKeys, nodes, edges, files, styleContext) {
|
|
788
3082
|
for (const file of files) {
|
|
3083
|
+
if ((0, extractors_1.isTestFile)(file.relPath)) {
|
|
3084
|
+
continue;
|
|
3085
|
+
}
|
|
789
3086
|
if (!file.uiElements.length && !file.classExpressions.length) {
|
|
790
3087
|
continue;
|
|
791
3088
|
}
|
|
@@ -819,6 +3116,46 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
819
3116
|
const classExpressionsByTarget = (0, extractors_1.groupClassExpressionsByTarget)(file.classExpressions);
|
|
820
3117
|
const layoutRoles = (0, utils_1.unique)(file.uiElements.flatMap((element) => element.layoutRole ?? []));
|
|
821
3118
|
const elementKinds = (0, utils_1.unique)(file.uiElements.map((element) => element.kind));
|
|
3119
|
+
const layoutSafety = summarizePatternLayoutSafety(file.uiElements);
|
|
3120
|
+
const responsiveProfile = summarizePatternResponsiveProfile(file.uiElements);
|
|
3121
|
+
const scaleProfile = summarizePatternScaleProfile(file.uiElements);
|
|
3122
|
+
const semanticProfile = summarizePatternSemanticProfile(file.uiElements);
|
|
3123
|
+
const parentContexts = summarizeParentContexts(file.uiElements);
|
|
3124
|
+
const childStructureContract = summarizePatternChildStructure(compositionElements, styleContext);
|
|
3125
|
+
const compoundStructure = summarizeCompoundStructure(file.uiElements, styleContext);
|
|
3126
|
+
const uiContract = createUiPatternContract({
|
|
3127
|
+
name: `${file.relPath} UI composition`,
|
|
3128
|
+
count: file.uiElements.length,
|
|
3129
|
+
kind: 'composition',
|
|
3130
|
+
tag: compositionElements[0]?.originalTag,
|
|
3131
|
+
layoutRole: layoutRoles[0],
|
|
3132
|
+
line: compositionElements[0]?.line,
|
|
3133
|
+
path: file.relPath,
|
|
3134
|
+
sourceHash: file.sourceHash,
|
|
3135
|
+
confidence: file.components.length || file.routes.length ? 'high' : 'medium',
|
|
3136
|
+
evidence: compositionElements[0]?.evidence,
|
|
3137
|
+
classSource: compositionElements[0]?.classSource,
|
|
3138
|
+
classes: baseClassReferences,
|
|
3139
|
+
defaultClasses: baseClassReferences,
|
|
3140
|
+
conditionalClasses,
|
|
3141
|
+
decorativeAccentClasses,
|
|
3142
|
+
classGroups: (0, extractors_1.mergeClassGroups)(compositionElements.map((element) => element.classGroups ?? {})),
|
|
3143
|
+
layoutSafety,
|
|
3144
|
+
responsiveProfile,
|
|
3145
|
+
scaleProfile,
|
|
3146
|
+
semanticProfile,
|
|
3147
|
+
styleContext,
|
|
3148
|
+
});
|
|
3149
|
+
uiContract.safetyRules = omitEmptyRecord({
|
|
3150
|
+
...uiContract.safetyRules,
|
|
3151
|
+
compositionGuidance: (0, extractors_1.createUiCompositionGuidance)(file),
|
|
3152
|
+
});
|
|
3153
|
+
const relationships = createUiRelationships({
|
|
3154
|
+
parentContext: parentContexts,
|
|
3155
|
+
childStructureContract,
|
|
3156
|
+
compoundStructure,
|
|
3157
|
+
graphKeys: dependencies,
|
|
3158
|
+
});
|
|
822
3159
|
addEntry(entries, usedKeys, {
|
|
823
3160
|
key: ruleKey,
|
|
824
3161
|
group: 'rule',
|
|
@@ -840,6 +3177,14 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
840
3177
|
...responsiveClasses.slice(0, 24),
|
|
841
3178
|
...surfaceClasses.slice(0, 24),
|
|
842
3179
|
...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
|
|
3180
|
+
...(layoutSafety
|
|
3181
|
+
? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
|
|
3182
|
+
: []),
|
|
3183
|
+
...(responsiveProfile ? ['responsive-contract'] : []),
|
|
3184
|
+
...(scaleProfile ? ['scale-contract'] : []),
|
|
3185
|
+
...(semanticProfile?.isCompound
|
|
3186
|
+
? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
|
|
3187
|
+
: []),
|
|
843
3188
|
...spacingClasses.slice(0, 24),
|
|
844
3189
|
...typographyClasses.slice(0, 24),
|
|
845
3190
|
]),
|
|
@@ -865,6 +3210,15 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
865
3210
|
conditionalClasses,
|
|
866
3211
|
decorativeAccentClasses,
|
|
867
3212
|
typographyClasses,
|
|
3213
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
3214
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
3215
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
3216
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
3217
|
+
...(parentContexts.length ? { parentContexts } : {}),
|
|
3218
|
+
...(childStructureContract ? { childStructureContract } : {}),
|
|
3219
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3220
|
+
uiContract,
|
|
3221
|
+
relationships,
|
|
868
3222
|
componentSlots: (0, extractors_1.buildComponentSlots)(file.uiElements),
|
|
869
3223
|
classExpressionsByTarget,
|
|
870
3224
|
elements: compositionElements,
|
|
@@ -883,6 +3237,10 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
883
3237
|
routes: file.routes,
|
|
884
3238
|
layoutRoles,
|
|
885
3239
|
elementKinds,
|
|
3240
|
+
layoutSafety,
|
|
3241
|
+
responsiveProfile,
|
|
3242
|
+
scaleProfile,
|
|
3243
|
+
semanticProfile,
|
|
886
3244
|
},
|
|
887
3245
|
}));
|
|
888
3246
|
edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_composition', 'ui.composition', file));
|
|
@@ -894,8 +3252,175 @@ function addUiCompositionRules(entries, usedKeys, nodes, edges, files) {
|
|
|
894
3252
|
}
|
|
895
3253
|
}
|
|
896
3254
|
}
|
|
897
|
-
function
|
|
898
|
-
const
|
|
3255
|
+
function addGlobalUiContractRules(entries, usedKeys, nodes, edges, files) {
|
|
3256
|
+
const eligibleFiles = files.filter((file) => !(0, extractors_1.isTestFile)(file.relPath));
|
|
3257
|
+
const shellFiles = eligibleFiles.filter(hasPageShellContractEvidence);
|
|
3258
|
+
const framedSurfaceFiles = eligibleFiles.filter(hasFramedSurfaceContractEvidence);
|
|
3259
|
+
if (shellFiles.length) {
|
|
3260
|
+
const primary = shellFiles[0];
|
|
3261
|
+
const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.page-shell.global');
|
|
3262
|
+
const cssRules = cssRuleSnapshots(shellFiles, shellCssRuleMatches).slice(0, 48);
|
|
3263
|
+
const classes = collectClassEvidence(shellFiles, (className) => /^(?:app-shell|bg-background|min-h-screen|flex|flex-col|text-foreground)$/.test(className));
|
|
3264
|
+
addEntry(entries, usedKeys, {
|
|
3265
|
+
key: ruleKey,
|
|
3266
|
+
group: 'rule',
|
|
3267
|
+
summary: 'Global page shell contract: body/app-shell owns the page background, route roots stay transparent.',
|
|
3268
|
+
tags: (0, utils_1.unique)([
|
|
3269
|
+
'rule',
|
|
3270
|
+
'ui',
|
|
3271
|
+
'global',
|
|
3272
|
+
'page-shell',
|
|
3273
|
+
'app-shell',
|
|
3274
|
+
'body-background',
|
|
3275
|
+
'route-root',
|
|
3276
|
+
'transparent-root',
|
|
3277
|
+
'bg-background',
|
|
3278
|
+
...classes,
|
|
3279
|
+
]),
|
|
3280
|
+
dependencies: contractDependencies(shellFiles),
|
|
3281
|
+
usedBy: [],
|
|
3282
|
+
confidence: 'high',
|
|
3283
|
+
status: 'inferred',
|
|
3284
|
+
path: primary.relPath,
|
|
3285
|
+
value: {
|
|
3286
|
+
name: 'Global page shell contract',
|
|
3287
|
+
kind: 'rule',
|
|
3288
|
+
ruleType: 'ui-page-shell',
|
|
3289
|
+
backgroundOwner: 'body/app-shell',
|
|
3290
|
+
keepRouteRootsTransparent: true,
|
|
3291
|
+
classes,
|
|
3292
|
+
requiredShellClasses: collectClassEvidence(shellFiles, (className) => /^(?:app-shell|min-h-screen|flex|flex-col)$/.test(className)),
|
|
3293
|
+
avoidOnRouteRoot: ['bg-background', 'min-h-screen bg-background'],
|
|
3294
|
+
routeRootGuidance: [
|
|
3295
|
+
'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.',
|
|
3296
|
+
'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.',
|
|
3297
|
+
'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.',
|
|
3298
|
+
],
|
|
3299
|
+
cssRules,
|
|
3300
|
+
sourceHash: primary.sourceHash,
|
|
3301
|
+
source: sourceInfo('ui.global-contracts', 'high', shellFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
|
|
3302
|
+
},
|
|
3303
|
+
});
|
|
3304
|
+
nodes.push(toGraphNode(ruleKey, 'rule', 'Global page shell contract', primary.relPath, primary, 'ui.global-contracts', {
|
|
3305
|
+
ui: {
|
|
3306
|
+
ruleType: 'ui-page-shell',
|
|
3307
|
+
backgroundOwner: 'body/app-shell',
|
|
3308
|
+
keepRouteRootsTransparent: true,
|
|
3309
|
+
classes,
|
|
3310
|
+
},
|
|
3311
|
+
}));
|
|
3312
|
+
for (const file of shellFiles.slice(0, 80)) {
|
|
3313
|
+
edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3314
|
+
if (hasStyleSurface(file)) {
|
|
3315
|
+
edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
if (framedSurfaceFiles.length) {
|
|
3320
|
+
const primary = framedSurfaceFiles[0];
|
|
3321
|
+
const ruleKey = (0, utils_1.toRegistryKey)('rule', 'ui.surface.section-frame-grid');
|
|
3322
|
+
const cssRules = cssRuleSnapshots(framedSurfaceFiles, framedSurfaceCssRuleMatches).slice(0, 48);
|
|
3323
|
+
const classes = collectClassEvidence(framedSurfaceFiles, (className) => /^(?:section-frame|sm:section-frame|context-surface|bg-card|bg-\[var\(--surface-soft\)\]|rounded)/.test(className));
|
|
3324
|
+
addEntry(entries, usedKeys, {
|
|
3325
|
+
key: ruleKey,
|
|
3326
|
+
group: 'rule',
|
|
3327
|
+
summary: 'Framed technical surface contract: section-frame paints a clipped grid overlay and context-surface protects child content.',
|
|
3328
|
+
tags: (0, utils_1.unique)([
|
|
3329
|
+
'rule',
|
|
3330
|
+
'ui',
|
|
3331
|
+
'surface',
|
|
3332
|
+
'framed-surface',
|
|
3333
|
+
'section-frame',
|
|
3334
|
+
'context-surface',
|
|
3335
|
+
'grid-overlay',
|
|
3336
|
+
'pseudo-element',
|
|
3337
|
+
'nested-surface',
|
|
3338
|
+
...classes,
|
|
3339
|
+
]),
|
|
3340
|
+
dependencies: contractDependencies(framedSurfaceFiles),
|
|
3341
|
+
usedBy: [],
|
|
3342
|
+
confidence: 'high',
|
|
3343
|
+
status: 'inferred',
|
|
3344
|
+
path: primary.relPath,
|
|
3345
|
+
value: {
|
|
3346
|
+
name: 'Section frame grid surface contract',
|
|
3347
|
+
kind: 'rule',
|
|
3348
|
+
ruleType: 'ui-framed-surface',
|
|
3349
|
+
surfaceClasses: ['section-frame', 'context-surface'],
|
|
3350
|
+
classes,
|
|
3351
|
+
paintModel: {
|
|
3352
|
+
sectionFrame: 'section-frame is not only border/background; it owns the clipped section-frame::before grid overlay for that exact surface.',
|
|
3353
|
+
contextSurface: 'context-surface keeps direct children above the parent overlay, preserving readable content and separate child surfaces.',
|
|
3354
|
+
},
|
|
3355
|
+
nestingGuidance: [
|
|
3356
|
+
'For parent technical panels, use section-frame with context-surface when content or child cards must sit above the parent grid.',
|
|
3357
|
+
'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.',
|
|
3358
|
+
'Do not rely on one ancestor section-frame grid for child cards that visually have separate framed grids in the source design.',
|
|
3359
|
+
],
|
|
3360
|
+
usageGuidance: [
|
|
3361
|
+
'Use this contract for framed technical panels and code/context surfaces, not every ordinary repeated card.',
|
|
3362
|
+
'Preserve responsive forms such as sm:section-frame when the source intentionally keeps mobile surfaces transparent or unframed.',
|
|
3363
|
+
],
|
|
3364
|
+
avoid: [
|
|
3365
|
+
'Do not add decorative glow/shadow classes to repeated cards just because they are framed.',
|
|
3366
|
+
'Do not flatten parent and child frame classes onto one root; preserve the surface hierarchy.',
|
|
3367
|
+
],
|
|
3368
|
+
cssRules,
|
|
3369
|
+
sourceHash: primary.sourceHash,
|
|
3370
|
+
source: sourceInfo('ui.global-contracts', 'high', framedSurfaceFiles.slice(0, 20).map((file) => `path:${file.relPath}`), primary),
|
|
3371
|
+
},
|
|
3372
|
+
});
|
|
3373
|
+
nodes.push(toGraphNode(ruleKey, 'rule', 'Section frame grid surface contract', primary.relPath, primary, 'ui.global-contracts', {
|
|
3374
|
+
ui: {
|
|
3375
|
+
ruleType: 'ui-framed-surface',
|
|
3376
|
+
surfaceClasses: ['section-frame', 'context-surface'],
|
|
3377
|
+
classes,
|
|
3378
|
+
},
|
|
3379
|
+
}));
|
|
3380
|
+
for (const file of framedSurfaceFiles.slice(0, 80)) {
|
|
3381
|
+
edges.push(toGraphEdge(file.key, ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3382
|
+
if (hasStyleSurface(file)) {
|
|
3383
|
+
edges.push(toGraphEdge(styleKeyForFile(file), ruleKey, 'defines_ui_contract', 'ui.global-contracts', file));
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
function hasPageShellContractEvidence(file) {
|
|
3389
|
+
return Boolean(file.classReferences.includes('app-shell') ||
|
|
3390
|
+
file.cssClasses.includes('app-shell') ||
|
|
3391
|
+
file.cssRules.some(shellCssRuleMatches) ||
|
|
3392
|
+
(file.relPath.endsWith('layout.tsx') && file.classReferences.includes('bg-background')));
|
|
3393
|
+
}
|
|
3394
|
+
function hasFramedSurfaceContractEvidence(file) {
|
|
3395
|
+
return Boolean(file.classReferences.some((className) => ['section-frame', 'sm:section-frame', 'context-surface'].includes(className)) ||
|
|
3396
|
+
file.cssClasses.some((className) => ['section-frame', 'context-surface'].includes(className)) ||
|
|
3397
|
+
file.cssRules.some(framedSurfaceCssRuleMatches));
|
|
3398
|
+
}
|
|
3399
|
+
function shellCssRuleMatches(rule) {
|
|
3400
|
+
return /(^|[,\s])(?:html|body|:root)(?:$|[,\s:{.#])|\.app-shell\b|\.bg-background\b/.test(rule.selector);
|
|
3401
|
+
}
|
|
3402
|
+
function framedSurfaceCssRuleMatches(rule) {
|
|
3403
|
+
return /\.section-frame\b|\.context-surface\b/.test(rule.selector);
|
|
3404
|
+
}
|
|
3405
|
+
function contractDependencies(files) {
|
|
3406
|
+
return (0, utils_1.unique)(files.flatMap((file) => [file.key, ...(hasStyleSurface(file) ? [styleKeyForFile(file)] : [])]));
|
|
3407
|
+
}
|
|
3408
|
+
function styleKeyForFile(file) {
|
|
3409
|
+
return (0, utils_1.toRegistryKey)('style', file.relPath.replace(/\.[^.]+$/, ''));
|
|
3410
|
+
}
|
|
3411
|
+
function collectClassEvidence(files, predicate) {
|
|
3412
|
+
return (0, utils_1.unique)(files.flatMap((file) => [...file.classReferences, ...file.cssClasses].filter(predicate))).slice(0, 120);
|
|
3413
|
+
}
|
|
3414
|
+
function cssRuleSnapshots(files, predicate) {
|
|
3415
|
+
return (0, utils_1.uniqueBy)(files.flatMap((file) => file.cssRules.filter(predicate).map((rule) => ({
|
|
3416
|
+
path: file.relPath,
|
|
3417
|
+
selector: rule.selector,
|
|
3418
|
+
line: rule.line,
|
|
3419
|
+
declarations: rule.declarations.slice(0, 24),
|
|
3420
|
+
}))), (rule) => `${rule.path}:${rule.selector}:${rule.line}`);
|
|
3421
|
+
}
|
|
3422
|
+
function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems, files, styleContext) {
|
|
3423
|
+
const clusters = buildConnectedPatternClusters(files, styleContext);
|
|
899
3424
|
for (const cluster of clusters) {
|
|
900
3425
|
addEntry(entries, usedKeys, {
|
|
901
3426
|
key: cluster.key,
|
|
@@ -967,9 +3492,9 @@ function addConnectedPatternRules(entries, usedKeys, nodes, edges, reviewItems,
|
|
|
967
3492
|
}
|
|
968
3493
|
}
|
|
969
3494
|
}
|
|
970
|
-
function buildConnectedPatternClusters(files) {
|
|
3495
|
+
function buildConnectedPatternClusters(files, styleContext) {
|
|
971
3496
|
const instances = [
|
|
972
|
-
...files.flatMap(createFrontendPatternInstances),
|
|
3497
|
+
...files.flatMap((file) => createFrontendPatternInstances(file, styleContext)),
|
|
973
3498
|
...createBackendPatternInstances(files),
|
|
974
3499
|
...createDataPatternInstances(files),
|
|
975
3500
|
];
|
|
@@ -990,7 +3515,62 @@ function buildConnectedPatternClusters(files) {
|
|
|
990
3515
|
const classGroups = (0, extractors_1.mergeClassGroups)(group
|
|
991
3516
|
.map((instance) => instance.root?.classGroups)
|
|
992
3517
|
.filter((value) => Boolean(value)));
|
|
3518
|
+
const layoutSafety = summarizeConnectedLayoutSafety(group);
|
|
3519
|
+
const responsiveProfile = summarizeConnectedResponsiveProfile(group);
|
|
3520
|
+
const scaleProfile = summarizeConnectedScaleProfile(group);
|
|
3521
|
+
const semanticProfile = summarizeConnectedSemanticProfile(group);
|
|
3522
|
+
const roleSignatures = summarizeConnectedRoleSignatures(group);
|
|
3523
|
+
const roleSignature = roleSignatures[0];
|
|
3524
|
+
const compoundStructure = summarizeConnectedCompoundStructure(group);
|
|
3525
|
+
const parentContexts = summarizeConnectedParentContexts(group);
|
|
993
3526
|
const childStructure = summarizeConnectedChildren(group);
|
|
3527
|
+
const graphKeys = (0, utils_1.unique)(group.flatMap((instance) => instance.graphKeys)).slice(0, 80);
|
|
3528
|
+
const structureContract = {
|
|
3529
|
+
preserveRootAndChildrenTogether: true,
|
|
3530
|
+
preserveParentLayoutContext: Boolean(parentContexts.length),
|
|
3531
|
+
preserveDirectChildRelationships: Boolean(childStructure.length),
|
|
3532
|
+
preserveCompoundChildRoles: Boolean(compoundStructure),
|
|
3533
|
+
preserveResponsiveBreakpoints: Boolean(responsiveProfile),
|
|
3534
|
+
preserveLayoutSafety: Boolean(layoutSafety),
|
|
3535
|
+
preserveScaleBounds: Boolean(scaleProfile),
|
|
3536
|
+
preserveRoleSignature: Boolean(roleSignature),
|
|
3537
|
+
avoidDefaultDecorativeAccents: Boolean(decorativeAccentClasses.length),
|
|
3538
|
+
};
|
|
3539
|
+
const uiContract = createUiPatternContract({
|
|
3540
|
+
name: `${(0, utils_1.titleCase)(first.concept)} connected pattern`,
|
|
3541
|
+
count: group.length,
|
|
3542
|
+
kind: first.concept,
|
|
3543
|
+
tag: first.root?.tag,
|
|
3544
|
+
layoutRole: first.root?.layoutRole,
|
|
3545
|
+
path: first.file.relPath,
|
|
3546
|
+
sourceHash: first.file.sourceHash,
|
|
3547
|
+
confidence,
|
|
3548
|
+
evidence: first.evidence.label,
|
|
3549
|
+
classes: defaultClasses,
|
|
3550
|
+
defaultClasses,
|
|
3551
|
+
conditionalClasses,
|
|
3552
|
+
decorativeAccentClasses,
|
|
3553
|
+
classGroups,
|
|
3554
|
+
layoutSafety,
|
|
3555
|
+
responsiveProfile,
|
|
3556
|
+
scaleProfile,
|
|
3557
|
+
semanticProfile,
|
|
3558
|
+
roleSignature,
|
|
3559
|
+
styleContext,
|
|
3560
|
+
});
|
|
3561
|
+
uiContract.safetyRules = omitEmptyRecord({
|
|
3562
|
+
...uiContract.safetyRules,
|
|
3563
|
+
structureContract,
|
|
3564
|
+
});
|
|
3565
|
+
const relationships = createUiRelationships({
|
|
3566
|
+
parentContext: parentContexts,
|
|
3567
|
+
childStructureContract: {
|
|
3568
|
+
children: childStructure,
|
|
3569
|
+
preserveDirectChildRelationships: Boolean(childStructure.length),
|
|
3570
|
+
},
|
|
3571
|
+
compoundStructure,
|
|
3572
|
+
graphKeys,
|
|
3573
|
+
});
|
|
994
3574
|
const key = (0, utils_1.toRegistryKey)('cluster', `pattern.${first.domain}.${first.concept}.${connectedPatternSlug(first)}.${(0, utils_1.shortHash)(first.signature)}`);
|
|
995
3575
|
const fileCount = new Set(group.map((instance) => instance.file.relPath)).size;
|
|
996
3576
|
clusters.push({
|
|
@@ -1015,6 +3595,28 @@ function buildConnectedPatternClusters(files) {
|
|
|
1015
3595
|
...group.flatMap((instance) => instance.tags).slice(0, 80),
|
|
1016
3596
|
...defaultClasses.slice(0, 24),
|
|
1017
3597
|
...(decorativeAccentClasses.length ? ['decorative-accent', 'highlight-only'] : []),
|
|
3598
|
+
...(layoutSafety
|
|
3599
|
+
? ['layout-safety', ...layoutSafety.layoutSafetyClasses.slice(0, 24)]
|
|
3600
|
+
: []),
|
|
3601
|
+
...(responsiveProfile ? ['responsive-contract'] : []),
|
|
3602
|
+
...(scaleProfile ? ['scale-contract'] : []),
|
|
3603
|
+
...(semanticProfile?.isCompound
|
|
3604
|
+
? ['compound-ui', 'compound-badge', ...semanticProfile.roles]
|
|
3605
|
+
: []),
|
|
3606
|
+
...(roleSignatures.length
|
|
3607
|
+
? [
|
|
3608
|
+
'role-signature',
|
|
3609
|
+
...roleSignatures.flatMap((signature) => [
|
|
3610
|
+
signature.role,
|
|
3611
|
+
signature.roleGroup,
|
|
3612
|
+
signature.scale,
|
|
3613
|
+
signature.density,
|
|
3614
|
+
signature.surface,
|
|
3615
|
+
signature.layout,
|
|
3616
|
+
...signature.flags,
|
|
3617
|
+
]),
|
|
3618
|
+
]
|
|
3619
|
+
: []),
|
|
1018
3620
|
]),
|
|
1019
3621
|
dependencies,
|
|
1020
3622
|
value: {
|
|
@@ -1034,9 +3636,19 @@ function buildConnectedPatternClusters(files) {
|
|
|
1034
3636
|
conditionalClasses,
|
|
1035
3637
|
decorativeAccentClasses,
|
|
1036
3638
|
classGroups,
|
|
3639
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
3640
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
3641
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
3642
|
+
...(semanticProfile ? { semanticProfile } : {}),
|
|
3643
|
+
...(roleSignatures.length ? { roleSignatures } : {}),
|
|
3644
|
+
...(parentContexts.length ? { parentContexts } : {}),
|
|
1037
3645
|
childStructure,
|
|
3646
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3647
|
+
structureContract,
|
|
3648
|
+
uiContract,
|
|
3649
|
+
relationships,
|
|
1038
3650
|
examples,
|
|
1039
|
-
graphKeys
|
|
3651
|
+
graphKeys,
|
|
1040
3652
|
guidance: createConnectedPatternGuidance(first, group, decorativeAccentClasses),
|
|
1041
3653
|
variants: connectedPatternVariants(first.domain, group),
|
|
1042
3654
|
children: connectedPatternChildren(first.domain, first.concept, group, dependencies),
|
|
@@ -1080,8 +3692,11 @@ function buildConnectedPatternClusters(files) {
|
|
|
1080
3692
|
}
|
|
1081
3693
|
return clusters.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
|
|
1082
3694
|
}
|
|
1083
|
-
function createFrontendPatternInstances(file) {
|
|
3695
|
+
function createFrontendPatternInstances(file, styleContext) {
|
|
1084
3696
|
const instances = [];
|
|
3697
|
+
if ((0, extractors_1.isTestFile)(file.relPath)) {
|
|
3698
|
+
return instances;
|
|
3699
|
+
}
|
|
1085
3700
|
for (const element of file.uiElements) {
|
|
1086
3701
|
if (!isConnectedUiPatternRoot(element)) {
|
|
1087
3702
|
continue;
|
|
@@ -1093,7 +3708,14 @@ function createFrontendPatternInstances(file) {
|
|
|
1093
3708
|
...(0, extractors_1.filterClasses)(element.classes, extractors_1.isDecorativeAccentClass),
|
|
1094
3709
|
]).slice(0, 60);
|
|
1095
3710
|
const classGroups = (0, extractors_1.groupTailwindClasses)(defaultClasses.length ? defaultClasses : element.classes);
|
|
1096
|
-
const children = normalizeConnectedChildren(element.childSummary ?? []);
|
|
3711
|
+
const children = normalizeConnectedChildren(element.childSummary ?? [], styleContext);
|
|
3712
|
+
const parentContext = summarizeParentContexts([element])[0];
|
|
3713
|
+
const childStructureContract = element.childSummary?.length
|
|
3714
|
+
? summarizeElementChildContract(element.childSummary, styleContext)
|
|
3715
|
+
: undefined;
|
|
3716
|
+
const compoundStructure = element.semanticProfile?.isCompound
|
|
3717
|
+
? summarizeCompoundStructure([element], styleContext)
|
|
3718
|
+
: undefined;
|
|
1097
3719
|
const component = file.components[0];
|
|
1098
3720
|
const route = file.routes[0];
|
|
1099
3721
|
const graphKeys = (0, utils_1.unique)([
|
|
@@ -1110,10 +3732,44 @@ function createFrontendPatternInstances(file) {
|
|
|
1110
3732
|
'frontend',
|
|
1111
3733
|
element.kind,
|
|
1112
3734
|
normalizeLayoutRole(element.layoutRole),
|
|
3735
|
+
roleSignatureKey(element.roleSignature),
|
|
1113
3736
|
normalizeTagForSignature(element),
|
|
1114
3737
|
classSignature(defaultClasses),
|
|
1115
3738
|
children.map((child) => child.signature).join('|'),
|
|
1116
3739
|
].join(';');
|
|
3740
|
+
const uiContract = createUiContract({
|
|
3741
|
+
kind: element.kind,
|
|
3742
|
+
tag: element.originalTag,
|
|
3743
|
+
layoutRole: element.layoutRole,
|
|
3744
|
+
label: element.label,
|
|
3745
|
+
line: element.line,
|
|
3746
|
+
path: file.relPath,
|
|
3747
|
+
sourceHash: file.sourceHash,
|
|
3748
|
+
confidence: 'medium',
|
|
3749
|
+
evidence: element.evidence,
|
|
3750
|
+
classSource: element.classSource,
|
|
3751
|
+
classes: element.classes,
|
|
3752
|
+
defaultClasses,
|
|
3753
|
+
conditionalClasses,
|
|
3754
|
+
decorativeAccentClasses,
|
|
3755
|
+
classGroups,
|
|
3756
|
+
layoutSafety: element.layoutSafety,
|
|
3757
|
+
responsiveProfile: element.responsiveProfile,
|
|
3758
|
+
scaleProfile: element.scaleProfile,
|
|
3759
|
+
semanticProfile: element.semanticProfile,
|
|
3760
|
+
roleSignature: element.roleSignature,
|
|
3761
|
+
variants: element.variants,
|
|
3762
|
+
props: element.props,
|
|
3763
|
+
styleHelper: element.styleHelper,
|
|
3764
|
+
styleHelperVariants: element.styleHelperVariants,
|
|
3765
|
+
styleContext,
|
|
3766
|
+
});
|
|
3767
|
+
const relationships = createUiRelationships({
|
|
3768
|
+
parentContext,
|
|
3769
|
+
childStructureContract,
|
|
3770
|
+
compoundStructure,
|
|
3771
|
+
graphKeys,
|
|
3772
|
+
});
|
|
1117
3773
|
instances.push({
|
|
1118
3774
|
domain: 'frontend',
|
|
1119
3775
|
concept: element.kind,
|
|
@@ -1129,6 +3785,12 @@ function createFrontendPatternInstances(file) {
|
|
|
1129
3785
|
conditionalClasses,
|
|
1130
3786
|
decorativeAccentClasses,
|
|
1131
3787
|
classGroups,
|
|
3788
|
+
...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
|
|
3789
|
+
...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
|
|
3790
|
+
...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
|
|
3791
|
+
...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
|
|
3792
|
+
...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
|
|
3793
|
+
uiContract,
|
|
1132
3794
|
},
|
|
1133
3795
|
children: children.map(withoutConnectedChildSignature),
|
|
1134
3796
|
graphKeys,
|
|
@@ -1142,6 +3804,25 @@ function createFrontendPatternInstances(file) {
|
|
|
1142
3804
|
...file.components,
|
|
1143
3805
|
...file.routes,
|
|
1144
3806
|
...Object.keys(classGroups),
|
|
3807
|
+
...(element.layoutSafety ? ['layout-safety'] : []),
|
|
3808
|
+
...(element.responsiveProfile ? ['responsive-contract'] : []),
|
|
3809
|
+
...(element.scaleProfile ? ['scale-contract'] : []),
|
|
3810
|
+
...(element.roleSignature
|
|
3811
|
+
? [
|
|
3812
|
+
'role-signature',
|
|
3813
|
+
element.roleSignature.role,
|
|
3814
|
+
element.roleSignature.roleGroup,
|
|
3815
|
+
element.roleSignature.scale,
|
|
3816
|
+
element.roleSignature.density,
|
|
3817
|
+
element.roleSignature.surface,
|
|
3818
|
+
element.roleSignature.layout,
|
|
3819
|
+
...element.roleSignature.flags,
|
|
3820
|
+
...element.roleSignature.exactClassFacts.slice(0, 24),
|
|
3821
|
+
]
|
|
3822
|
+
: []),
|
|
3823
|
+
...(element.semanticProfile?.isCompound
|
|
3824
|
+
? ['compound-ui', 'compound-badge', ...element.semanticProfile.roles]
|
|
3825
|
+
: []),
|
|
1145
3826
|
]),
|
|
1146
3827
|
evidence: {
|
|
1147
3828
|
path: file.relPath,
|
|
@@ -1160,8 +3841,18 @@ function createFrontendPatternInstances(file) {
|
|
|
1160
3841
|
conditionalClasses,
|
|
1161
3842
|
decorativeAccentClasses,
|
|
1162
3843
|
classGroups,
|
|
3844
|
+
...(element.layoutSafety ? { layoutSafety: element.layoutSafety } : {}),
|
|
3845
|
+
...(element.responsiveProfile ? { responsiveProfile: element.responsiveProfile } : {}),
|
|
3846
|
+
...(element.scaleProfile ? { scaleProfile: element.scaleProfile } : {}),
|
|
3847
|
+
...(element.semanticProfile ? { semanticProfile: element.semanticProfile } : {}),
|
|
3848
|
+
...(element.roleSignature ? { roleSignature: element.roleSignature } : {}),
|
|
3849
|
+
uiContract,
|
|
1163
3850
|
},
|
|
1164
3851
|
children: children.map(withoutConnectedChildSignature),
|
|
3852
|
+
...(parentContext ? { parentContext } : {}),
|
|
3853
|
+
...(childStructureContract ? { childStructureContract } : {}),
|
|
3854
|
+
...(compoundStructure ? { compoundStructure } : {}),
|
|
3855
|
+
relationships,
|
|
1165
3856
|
},
|
|
1166
3857
|
baseWeight: frontendPatternWeight(file, element, children.length, defaultClasses),
|
|
1167
3858
|
});
|
|
@@ -1185,6 +3876,7 @@ function createBackendPatternInstances(files) {
|
|
|
1185
3876
|
...file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
|
|
1186
3877
|
...file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
|
|
1187
3878
|
...file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
|
|
3879
|
+
...file.databaseAccesses.flatMap(databaseAccessDependencies),
|
|
1188
3880
|
...file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
|
|
1189
3881
|
]);
|
|
1190
3882
|
const signature = [
|
|
@@ -1195,7 +3887,7 @@ function createBackendPatternInstances(files) {
|
|
|
1195
3887
|
`methods:${methodShapes.join(',') || 'none'}`,
|
|
1196
3888
|
`controller:${Boolean(file.controllers.length)}`,
|
|
1197
3889
|
`service:${Boolean(file.services.length)}`,
|
|
1198
|
-
`model:${Boolean(file.models.length)}`,
|
|
3890
|
+
`model:${Boolean(file.models.length || file.databaseAccesses.length)}`,
|
|
1199
3891
|
`auth:${Boolean(file.authHints.length || file.middleware.length)}`,
|
|
1200
3892
|
`events:${Boolean(file.events.length || file.jobs.length)}`,
|
|
1201
3893
|
`calls:${Boolean(file.apiCalls.length)}`,
|
|
@@ -1234,6 +3926,7 @@ function createBackendPatternInstances(files) {
|
|
|
1234
3926
|
modules: file.modules,
|
|
1235
3927
|
routes: file.routes,
|
|
1236
3928
|
models: file.models,
|
|
3929
|
+
databaseAccesses: file.databaseAccesses,
|
|
1237
3930
|
apiCalls: file.apiCalls,
|
|
1238
3931
|
envVars: file.envVars,
|
|
1239
3932
|
authHints: file.authHints,
|
|
@@ -1258,12 +3951,19 @@ function createDataPatternInstances(files) {
|
|
|
1258
3951
|
const instances = [];
|
|
1259
3952
|
for (const file of files) {
|
|
1260
3953
|
for (const model of file.models) {
|
|
1261
|
-
const
|
|
3954
|
+
const databaseModel = file.databaseModels.find((candidate) => candidate.name === model);
|
|
3955
|
+
const fields = databaseModel
|
|
3956
|
+
? databaseFieldShapeInputs(databaseModel)
|
|
3957
|
+
: (file.modelFields[model] ?? []);
|
|
1262
3958
|
const fieldShape = summarizeModelFieldShape(fields);
|
|
3959
|
+
const accessGraphKeys = file.databaseAccesses
|
|
3960
|
+
.filter((access) => access.model === model || access.table === databaseModel?.tableName)
|
|
3961
|
+
.map((access) => (0, utils_1.toRegistryKey)('database', `access.${file.relPath.replace(/\.[^.]+$/, '')}.${access.line}.${access.operation}.${access.model ?? access.table ?? access.receiver ?? 'unknown'}`));
|
|
1263
3962
|
const signature = [
|
|
1264
3963
|
'data',
|
|
1265
|
-
'model',
|
|
1266
|
-
detectFrameworkForFile(file) ?? file.language,
|
|
3964
|
+
databaseModel?.kind ?? 'model',
|
|
3965
|
+
databaseModel?.provider ?? detectFrameworkForFile(file) ?? file.language,
|
|
3966
|
+
databaseModel?.tableName ?? 'no-table',
|
|
1267
3967
|
fieldShape.signature,
|
|
1268
3968
|
].join(';');
|
|
1269
3969
|
instances.push({
|
|
@@ -1272,8 +3972,24 @@ function createDataPatternInstances(files) {
|
|
|
1272
3972
|
signature,
|
|
1273
3973
|
summary: `${model} data model shape in ${file.relPath}.`,
|
|
1274
3974
|
file,
|
|
1275
|
-
graphKeys: (0, utils_1.unique)([
|
|
1276
|
-
|
|
3975
|
+
graphKeys: (0, utils_1.unique)([
|
|
3976
|
+
file.key,
|
|
3977
|
+
(0, utils_1.toRegistryKey)('model', model),
|
|
3978
|
+
...(hasDatabaseSurface(file)
|
|
3979
|
+
? [(0, utils_1.toRegistryKey)('database', file.relPath.replace(/\.[^.]+$/, ''))]
|
|
3980
|
+
: []),
|
|
3981
|
+
...accessGraphKeys,
|
|
3982
|
+
]),
|
|
3983
|
+
tags: (0, utils_1.unique)([
|
|
3984
|
+
'data',
|
|
3985
|
+
'database',
|
|
3986
|
+
databaseModel?.kind ?? 'model',
|
|
3987
|
+
'schema',
|
|
3988
|
+
model,
|
|
3989
|
+
databaseModel?.tableName ?? '',
|
|
3990
|
+
databaseModel?.provider ?? file.language,
|
|
3991
|
+
...fieldShape.tags,
|
|
3992
|
+
]),
|
|
1277
3993
|
evidence: {
|
|
1278
3994
|
path: file.relPath,
|
|
1279
3995
|
key: (0, utils_1.toRegistryKey)('model', model),
|
|
@@ -1281,18 +3997,35 @@ function createDataPatternInstances(files) {
|
|
|
1281
3997
|
},
|
|
1282
3998
|
value: {
|
|
1283
3999
|
model,
|
|
1284
|
-
fields,
|
|
4000
|
+
fields: databaseModel?.fieldNames ?? fields,
|
|
4001
|
+
fieldDetails: databaseModel?.fields ?? [],
|
|
1285
4002
|
fieldShape,
|
|
1286
|
-
|
|
4003
|
+
tableName: databaseModel?.tableName,
|
|
4004
|
+
provider: databaseModel?.provider,
|
|
4005
|
+
primaryKey: databaseModel?.primaryKey ?? [],
|
|
4006
|
+
uniqueFields: databaseModel?.uniqueFields ?? [],
|
|
4007
|
+
indexes: databaseModel?.indexes ?? [],
|
|
4008
|
+
relations: databaseModel?.relations ?? [],
|
|
4009
|
+
enumRefs: databaseModel?.enumRefs ?? [],
|
|
4010
|
+
accesses: file.databaseAccesses.filter((access) => access.model === model || access.table === databaseModel?.tableName),
|
|
4011
|
+
framework: databaseModel?.framework ?? detectFrameworkForFile(file),
|
|
1287
4012
|
},
|
|
1288
|
-
baseWeight: 3 +
|
|
4013
|
+
baseWeight: 3 +
|
|
4014
|
+
Math.min(fields.length / 2, 8) +
|
|
4015
|
+
Math.min((databaseModel?.relations.length ?? 0) * 0.75, 4) +
|
|
4016
|
+
Math.min(accessGraphKeys.length * 0.5, 4),
|
|
1289
4017
|
});
|
|
1290
4018
|
}
|
|
1291
4019
|
}
|
|
1292
4020
|
return instances;
|
|
1293
4021
|
}
|
|
1294
4022
|
function isConnectedUiPatternRoot(element) {
|
|
1295
|
-
if (element.kind === 'unknown' ||
|
|
4023
|
+
if (element.kind === 'unknown' ||
|
|
4024
|
+
element.kind === 'link' ||
|
|
4025
|
+
element.kind === 'text' ||
|
|
4026
|
+
element.kind === 'icon' ||
|
|
4027
|
+
element.kind === 'image' ||
|
|
4028
|
+
element.kind === 'divider') {
|
|
1296
4029
|
return false;
|
|
1297
4030
|
}
|
|
1298
4031
|
if (['button', 'input', 'badge'].includes(element.kind)) {
|
|
@@ -1300,9 +4033,12 @@ function isConnectedUiPatternRoot(element) {
|
|
|
1300
4033
|
}
|
|
1301
4034
|
return (0, extractors_1.isCompositionElement)(element) || Boolean(element.childSummary?.length);
|
|
1302
4035
|
}
|
|
1303
|
-
function normalizeConnectedChildren(children) {
|
|
4036
|
+
function normalizeConnectedChildren(children, styleContext) {
|
|
1304
4037
|
return children
|
|
1305
|
-
.filter((child) => child.kind !== 'unknown' ||
|
|
4038
|
+
.filter((child) => child.kind !== 'unknown' ||
|
|
4039
|
+
child.classes.length ||
|
|
4040
|
+
child.label ||
|
|
4041
|
+
Boolean(child.semanticRole))
|
|
1306
4042
|
.slice(0, 10)
|
|
1307
4043
|
.map((child) => {
|
|
1308
4044
|
const defaultClasses = (0, extractors_1.filterClasses)(child.defaultClasses?.length ? child.defaultClasses : child.classes, (className) => !(0, extractors_1.isDecorativeAccentClass)(className)).slice(0, 48);
|
|
@@ -1311,6 +4047,8 @@ function normalizeConnectedChildren(children) {
|
|
|
1311
4047
|
const signature = [
|
|
1312
4048
|
normalizedKind,
|
|
1313
4049
|
normalizeLayoutRole(child.layoutRole),
|
|
4050
|
+
child.semanticRole ?? 'unspecified-semantic-role',
|
|
4051
|
+
roleSignatureKey(child.roleSignature),
|
|
1314
4052
|
normalizeChildTagForSignature(child),
|
|
1315
4053
|
classSignature(defaultClasses),
|
|
1316
4054
|
].join(':');
|
|
@@ -1320,8 +4058,17 @@ function normalizeConnectedChildren(children) {
|
|
|
1320
4058
|
sourceKind: child.kind,
|
|
1321
4059
|
tag: child.originalTag,
|
|
1322
4060
|
layoutRole: child.layoutRole,
|
|
4061
|
+
semanticRole: child.semanticRole,
|
|
4062
|
+
uiContract: createUiContractForChild(child, styleContext),
|
|
1323
4063
|
defaultClasses,
|
|
1324
4064
|
conditionalClasses: child.conditionalClasses?.slice(0, 32) ?? [],
|
|
4065
|
+
layoutSafety: child.layoutSafety,
|
|
4066
|
+
responsiveProfile: child.responsiveProfile,
|
|
4067
|
+
scaleProfile: child.scaleProfile,
|
|
4068
|
+
roleSignature: child.roleSignature,
|
|
4069
|
+
variants: child.variants ?? {},
|
|
4070
|
+
styleHelper: child.styleHelper,
|
|
4071
|
+
styleHelperVariants: child.styleHelperVariants ?? {},
|
|
1325
4072
|
classGroups,
|
|
1326
4073
|
labelHint: child.label ? labelSignature(child.label) : undefined,
|
|
1327
4074
|
};
|
|
@@ -1331,25 +4078,125 @@ function withoutConnectedChildSignature(child) {
|
|
|
1331
4078
|
return Object.fromEntries(Object.entries(child).filter(([key]) => key !== 'signature'));
|
|
1332
4079
|
}
|
|
1333
4080
|
function connectedChildKind(child) {
|
|
4081
|
+
if (child.semanticRole === 'icon' || child.semanticRole === 'component-icon') {
|
|
4082
|
+
return 'icon';
|
|
4083
|
+
}
|
|
4084
|
+
if (child.semanticRole === 'image') {
|
|
4085
|
+
return 'image';
|
|
4086
|
+
}
|
|
4087
|
+
if (child.semanticRole === 'divider') {
|
|
4088
|
+
return 'divider';
|
|
4089
|
+
}
|
|
1334
4090
|
if (child.layoutRole === 'eyebrow-or-badge') {
|
|
1335
4091
|
return 'badge';
|
|
1336
4092
|
}
|
|
1337
4093
|
return child.kind;
|
|
1338
4094
|
}
|
|
4095
|
+
function isRecordValue(value) {
|
|
4096
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
4097
|
+
}
|
|
4098
|
+
function isRoleSignatureRecord(value) {
|
|
4099
|
+
return (isRecordValue(value) &&
|
|
4100
|
+
typeof value.role === 'string' &&
|
|
4101
|
+
typeof value.roleGroup === 'string' &&
|
|
4102
|
+
typeof value.scale === 'string' &&
|
|
4103
|
+
typeof value.density === 'string' &&
|
|
4104
|
+
typeof value.surface === 'string' &&
|
|
4105
|
+
typeof value.layout === 'string' &&
|
|
4106
|
+
Array.isArray(value.flags) &&
|
|
4107
|
+
Array.isArray(value.exactClassFacts));
|
|
4108
|
+
}
|
|
4109
|
+
function summarizeConnectedLayoutSafety(instances) {
|
|
4110
|
+
return mergeLayoutSafetyProfiles([
|
|
4111
|
+
...instances.map((instance) => ({ layoutSafety: instance.root?.layoutSafety })),
|
|
4112
|
+
...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
|
|
4113
|
+
layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
|
|
4114
|
+
}))),
|
|
4115
|
+
]);
|
|
4116
|
+
}
|
|
4117
|
+
function summarizeConnectedResponsiveProfile(instances) {
|
|
4118
|
+
return mergeResponsiveProfiles([
|
|
4119
|
+
...instances.map((instance) => ({ responsiveProfile: instance.root?.responsiveProfile })),
|
|
4120
|
+
...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
|
|
4121
|
+
responsiveProfile: isResponsiveProfile(child.responsiveProfile)
|
|
4122
|
+
? child.responsiveProfile
|
|
4123
|
+
: undefined,
|
|
4124
|
+
}))),
|
|
4125
|
+
]);
|
|
4126
|
+
}
|
|
4127
|
+
function summarizeConnectedScaleProfile(instances) {
|
|
4128
|
+
return mergeScaleProfiles([
|
|
4129
|
+
...instances.map((instance) => ({ scaleProfile: instance.root?.scaleProfile })),
|
|
4130
|
+
...instances.flatMap((instance) => (instance.children ?? []).map((child) => ({
|
|
4131
|
+
scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
|
|
4132
|
+
}))),
|
|
4133
|
+
]);
|
|
4134
|
+
}
|
|
4135
|
+
function summarizeConnectedSemanticProfile(instances) {
|
|
4136
|
+
return mergeSemanticProfiles(instances.map((instance) => ({ semanticProfile: instance.root?.semanticProfile })));
|
|
4137
|
+
}
|
|
4138
|
+
function summarizeConnectedRoleSignatures(instances) {
|
|
4139
|
+
return (0, utils_1.uniqueBy)(instances
|
|
4140
|
+
.map((instance) => instance.root?.roleSignature)
|
|
4141
|
+
.filter((value) => Boolean(value)), (signature) => roleSignatureKey(signature)).slice(0, 12);
|
|
4142
|
+
}
|
|
4143
|
+
function summarizeConnectedCompoundStructure(instances) {
|
|
4144
|
+
const compoundInstances = instances.filter((instance) => instance.root?.semanticProfile?.isCompound);
|
|
4145
|
+
if (!compoundInstances.length) {
|
|
4146
|
+
return undefined;
|
|
4147
|
+
}
|
|
4148
|
+
const childCounts = compoundInstances.map((instance) => instance.children?.length ?? 0);
|
|
4149
|
+
const childOrderExamples = (0, utils_1.uniqueBy)(compoundInstances
|
|
4150
|
+
.map((instance) => instance.value.childStructureContract)
|
|
4151
|
+
.filter((value) => isRecordValue(value)), (value) => JSON.stringify(value)).slice(0, 8);
|
|
4152
|
+
const semanticProfile = summarizeConnectedSemanticProfile(compoundInstances);
|
|
4153
|
+
return {
|
|
4154
|
+
preserveCompoundChildren: true,
|
|
4155
|
+
preserveIconAndDividerRoles: Boolean(semanticProfile?.hasIcon || semanticProfile?.hasDivider),
|
|
4156
|
+
preserveMultipleTextSegments: Boolean(semanticProfile?.hasMultipleTextStyles),
|
|
4157
|
+
directChildCountRange: {
|
|
4158
|
+
min: Math.min(...childCounts),
|
|
4159
|
+
max: Math.max(...childCounts),
|
|
4160
|
+
},
|
|
4161
|
+
semanticRoles: semanticProfile?.roles ?? [],
|
|
4162
|
+
childOrderExamples,
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
function summarizeConnectedParentContexts(instances) {
|
|
4166
|
+
return (0, utils_1.uniqueBy)(instances
|
|
4167
|
+
.map((instance) => instance.value.parentContext)
|
|
4168
|
+
.filter((value) => isRecordValue(value)), (context) => JSON.stringify(context)).slice(0, 12);
|
|
4169
|
+
}
|
|
1339
4170
|
function summarizeConnectedChildren(instances) {
|
|
1340
4171
|
const childValues = instances.flatMap((instance) => instance.children ?? []);
|
|
1341
4172
|
const grouped = (0, utils_1.groupBy)(childValues, (child) => [
|
|
1342
4173
|
String(child.kind ?? ''),
|
|
1343
4174
|
String(child.layoutRole ?? ''),
|
|
4175
|
+
String(child.semanticRole ?? ''),
|
|
4176
|
+
roleSignatureKey(isRoleSignatureRecord(child.roleSignature) ? child.roleSignature : undefined),
|
|
1344
4177
|
JSON.stringify(child.classGroups ?? {}),
|
|
1345
4178
|
].join(':'));
|
|
1346
4179
|
return Array.from(grouped.values())
|
|
1347
4180
|
.map((children) => {
|
|
1348
4181
|
const first = children[0] ?? {};
|
|
4182
|
+
const layoutSafety = mergeLayoutSafetyProfiles(children.map((child) => ({
|
|
4183
|
+
layoutSafety: isLayoutSafetyProfile(child.layoutSafety) ? child.layoutSafety : undefined,
|
|
4184
|
+
})));
|
|
4185
|
+
const responsiveProfile = mergeResponsiveProfiles(children.map((child) => ({
|
|
4186
|
+
responsiveProfile: isResponsiveProfile(child.responsiveProfile)
|
|
4187
|
+
? child.responsiveProfile
|
|
4188
|
+
: undefined,
|
|
4189
|
+
})));
|
|
4190
|
+
const scaleProfile = mergeScaleProfiles(children.map((child) => ({
|
|
4191
|
+
scaleProfile: isScaleProfile(child.scaleProfile) ? child.scaleProfile : undefined,
|
|
4192
|
+
})));
|
|
1349
4193
|
return {
|
|
1350
4194
|
kind: first.kind,
|
|
1351
4195
|
tag: first.tag,
|
|
1352
4196
|
layoutRole: first.layoutRole,
|
|
4197
|
+
semanticRole: first.semanticRole,
|
|
4198
|
+
roleSignature: first.roleSignature,
|
|
4199
|
+
uiContract: first.uiContract,
|
|
1353
4200
|
count: children.length,
|
|
1354
4201
|
defaultClasses: (0, utils_1.unique)(children.flatMap((child) => Array.isArray(child.defaultClasses)
|
|
1355
4202
|
? child.defaultClasses.filter((item) => typeof item === 'string')
|
|
@@ -1357,11 +4204,29 @@ function summarizeConnectedChildren(instances) {
|
|
|
1357
4204
|
classGroups: (0, extractors_1.mergeClassGroups)(children
|
|
1358
4205
|
.map((child) => child.classGroups)
|
|
1359
4206
|
.filter((value) => Boolean(value))),
|
|
4207
|
+
...(layoutSafety ? { layoutSafety } : {}),
|
|
4208
|
+
...(responsiveProfile ? { responsiveProfile } : {}),
|
|
4209
|
+
...(scaleProfile ? { scaleProfile } : {}),
|
|
4210
|
+
variants: mergeChildVariants(children),
|
|
4211
|
+
styleHelpers: (0, utils_1.unique)(children
|
|
4212
|
+
.map((child) => child.styleHelper)
|
|
4213
|
+
.filter((value) => typeof value === 'string' && Boolean(value))).slice(0, 12),
|
|
1360
4214
|
};
|
|
1361
4215
|
})
|
|
1362
4216
|
.sort((a, b) => Number(b.count) - Number(a.count))
|
|
1363
4217
|
.slice(0, 16);
|
|
1364
4218
|
}
|
|
4219
|
+
function mergeChildVariants(children) {
|
|
4220
|
+
const variants = children
|
|
4221
|
+
.map((child) => child.variants)
|
|
4222
|
+
.filter((value) => {
|
|
4223
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
4224
|
+
return false;
|
|
4225
|
+
}
|
|
4226
|
+
return Object.values(value).every((item) => typeof item === 'string');
|
|
4227
|
+
});
|
|
4228
|
+
return (0, extractors_1.mergeVariantProps)(variants);
|
|
4229
|
+
}
|
|
1365
4230
|
function compactConnectedPatternInstance(instance) {
|
|
1366
4231
|
return {
|
|
1367
4232
|
path: instance.file.relPath,
|
|
@@ -1548,7 +4413,9 @@ function createConceptPatternDecision(name, scores, shouldReview, roleCandidateC
|
|
|
1548
4413
|
function connectedPatternVariants(domain, instances) {
|
|
1549
4414
|
const preferredPrefixes = domain === 'frontend'
|
|
1550
4415
|
? ['component.', 'route.', 'style.', 'rule.']
|
|
1551
|
-
:
|
|
4416
|
+
: domain === 'data'
|
|
4417
|
+
? ['database.', 'model.', 'service.', 'api.', 'route.']
|
|
4418
|
+
: ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.'];
|
|
1552
4419
|
return (0, utils_1.unique)(instances.flatMap((instance) => [
|
|
1553
4420
|
...instance.file.components.map((component) => (0, utils_1.toRegistryKey)('component', component)),
|
|
1554
4421
|
...instance.file.routes.map((route) => (0, utils_1.toRegistryKey)('route', route)),
|
|
@@ -1557,6 +4424,9 @@ function connectedPatternVariants(domain, instances) {
|
|
|
1557
4424
|
...instance.file.services.map((service) => (0, utils_1.toRegistryKey)('service', service)),
|
|
1558
4425
|
...instance.file.modules.map((moduleName) => (0, utils_1.toRegistryKey)('module', moduleName)),
|
|
1559
4426
|
...instance.file.models.map((model) => (0, utils_1.toRegistryKey)('model', model)),
|
|
4427
|
+
...(hasDatabaseSurface(instance.file)
|
|
4428
|
+
? [(0, utils_1.toRegistryKey)('database', instance.file.relPath.replace(/\.[^.]+$/, ''))]
|
|
4429
|
+
: []),
|
|
1560
4430
|
...instance.graphKeys.filter((key) => preferredPrefixes.some((prefix) => key.startsWith(prefix))),
|
|
1561
4431
|
])).slice(0, 80);
|
|
1562
4432
|
}
|
|
@@ -1572,6 +4442,9 @@ function connectedPatternChildren(domain, concept, instances, dependencies) {
|
|
|
1572
4442
|
const graphChildren = dependencies.filter((key) => ['component.', 'style.', 'rule.', 'route.'].some((prefix) => key.startsWith(prefix)));
|
|
1573
4443
|
return (0, utils_1.unique)([...semanticChildren, ...graphChildren]).slice(0, 80);
|
|
1574
4444
|
}
|
|
4445
|
+
if (domain === 'data') {
|
|
4446
|
+
return (0, utils_1.unique)(dependencies.filter((key) => ['database.', 'model.', 'service.', 'api.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
|
|
4447
|
+
}
|
|
1575
4448
|
return (0, utils_1.unique)(dependencies.filter((key) => ['api.', 'controller.', 'service.', 'module.', 'model.', 'route.', 'file.'].some((prefix) => key.startsWith(prefix)))).slice(0, 80);
|
|
1576
4449
|
}
|
|
1577
4450
|
function derivePatternOwners(filePaths) {
|
|
@@ -1635,6 +4508,7 @@ function backendPatternWeight(file) {
|
|
|
1635
4508
|
file.controllers.length * 2 +
|
|
1636
4509
|
file.services.length * 2 +
|
|
1637
4510
|
file.models.length * 1.5 +
|
|
4511
|
+
file.databaseAccesses.length * 1.2 +
|
|
1638
4512
|
file.authHints.length * 0.5 +
|
|
1639
4513
|
file.events.length * 0.5 +
|
|
1640
4514
|
file.jobs.length * 0.5);
|
|
@@ -1644,6 +4518,7 @@ function hasBackendPatternSurface(file) {
|
|
|
1644
4518
|
file.controllers.length ||
|
|
1645
4519
|
file.services.length ||
|
|
1646
4520
|
file.modules.length ||
|
|
4521
|
+
file.databaseAccesses.length ||
|
|
1647
4522
|
file.apiCalls.length ||
|
|
1648
4523
|
file.middleware.length ||
|
|
1649
4524
|
file.jobs.length ||
|
|
@@ -1651,13 +4526,17 @@ function hasBackendPatternSurface(file) {
|
|
|
1651
4526
|
}
|
|
1652
4527
|
function backendPatternConcept(file) {
|
|
1653
4528
|
if (file.controllers.length) {
|
|
1654
|
-
return file.services.length || file.models.length
|
|
4529
|
+
return file.services.length || file.models.length || file.databaseAccesses.length
|
|
4530
|
+
? 'api-controller-flow'
|
|
4531
|
+
: 'api-controller';
|
|
1655
4532
|
}
|
|
1656
4533
|
if (file.apiHandlers.length) {
|
|
1657
|
-
return file.models.length || file.
|
|
4534
|
+
return file.models.length || file.databaseAccesses.length || file.authHints.length
|
|
4535
|
+
? 'api-data-flow'
|
|
4536
|
+
: 'api-route';
|
|
1658
4537
|
}
|
|
1659
4538
|
if (file.services.length) {
|
|
1660
|
-
return file.models.length ? 'service-data-flow' : 'service';
|
|
4539
|
+
return file.models.length || file.databaseAccesses.length ? 'service-data-flow' : 'service';
|
|
1661
4540
|
}
|
|
1662
4541
|
if (file.jobs.length || file.events.length) {
|
|
1663
4542
|
return 'async-flow';
|
|
@@ -1667,6 +4546,22 @@ function backendPatternConcept(file) {
|
|
|
1667
4546
|
}
|
|
1668
4547
|
return 'backend-flow';
|
|
1669
4548
|
}
|
|
4549
|
+
function databaseFieldShapeInputs(model) {
|
|
4550
|
+
return model.fields.map((field) => [
|
|
4551
|
+
field.name,
|
|
4552
|
+
field.type ?? '',
|
|
4553
|
+
field.id ? '@id' : '',
|
|
4554
|
+
field.unique ? '@unique' : '',
|
|
4555
|
+
field.optional ? '?' : '',
|
|
4556
|
+
field.list ? '[]' : '',
|
|
4557
|
+
field.default ? `default:${field.default}` : '',
|
|
4558
|
+
field.enumName ? `enum:${field.enumName}` : '',
|
|
4559
|
+
field.relation ? '@relation' : '',
|
|
4560
|
+
field.relation?.onDelete ? `onDelete:${field.relation.onDelete}` : '',
|
|
4561
|
+
]
|
|
4562
|
+
.filter(Boolean)
|
|
4563
|
+
.join(' '));
|
|
4564
|
+
}
|
|
1670
4565
|
function summarizeModelFieldShape(fields) {
|
|
1671
4566
|
const counts = {
|
|
1672
4567
|
id: 0,
|
|
@@ -1718,7 +4613,25 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
|
|
|
1718
4613
|
`Pattern score is based on usage count, file spread, component/route evidence, and structural specificity.`,
|
|
1719
4614
|
];
|
|
1720
4615
|
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.');
|
|
4616
|
+
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.');
|
|
4617
|
+
if (summarizeConnectedLayoutSafety(instances)) {
|
|
4618
|
+
guidance.push('Preserve layoutSafety classes with their original element. They prevent wrapped buttons, overflowing card text, collapsed columns, and mobile clipping.');
|
|
4619
|
+
}
|
|
4620
|
+
if (summarizeConnectedParentContexts(instances).length) {
|
|
4621
|
+
guidance.push('Preserve parentContexts when placing this pattern. Repeated cards and nested panels should inherit the same grid/flex parent relationship as the source.');
|
|
4622
|
+
}
|
|
4623
|
+
if (summarizeConnectedScaleProfile(instances)) {
|
|
4624
|
+
guidance.push('Preserve scaleProfile for typography, spacing, max-width, and line-height. Do not make headings or section spacing larger than observed source bounds.');
|
|
4625
|
+
}
|
|
4626
|
+
if (summarizeConnectedRoleSignatures(instances).length) {
|
|
4627
|
+
guidance.push('Preserve roleSignature and roleConstraints as hard constraints. Lookalike patterns with different role, density, surface, layout, or exactClassFacts are different generation targets.');
|
|
4628
|
+
}
|
|
4629
|
+
if (summarizeConnectedResponsiveProfile(instances)) {
|
|
4630
|
+
guidance.push('Preserve responsiveProfile exactly enough that mobile and desktop layouts use the same breakpoints and visibility behavior as the source.');
|
|
4631
|
+
}
|
|
4632
|
+
if (summarizeConnectedCompoundStructure(instances)) {
|
|
4633
|
+
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.');
|
|
4634
|
+
}
|
|
1722
4635
|
}
|
|
1723
4636
|
if (decorativeAccentClasses.length) {
|
|
1724
4637
|
guidance.push('decorativeAccentClasses are opt-in highlight treatments. They must not become default card/surface styling.');
|
|
@@ -1737,6 +4650,7 @@ function createConnectedPatternGuidance(first, instances, decorativeAccentClasse
|
|
|
1737
4650
|
function connectedPatternSlug(instance) {
|
|
1738
4651
|
const parts = [
|
|
1739
4652
|
instance.concept,
|
|
4653
|
+
instance.root?.roleSignature?.role,
|
|
1740
4654
|
instance.root?.layoutRole,
|
|
1741
4655
|
instance.root?.tag,
|
|
1742
4656
|
...Object.keys(instance.root?.classGroups ?? {}).slice(0, 3),
|
|
@@ -1751,6 +4665,22 @@ function connectedPatternSlug(instance) {
|
|
|
1751
4665
|
.slice(0, 5)
|
|
1752
4666
|
.join('.') || instance.concept);
|
|
1753
4667
|
}
|
|
4668
|
+
function roleSignatureKey(signature) {
|
|
4669
|
+
if (!signature) {
|
|
4670
|
+
return 'role-signature:unspecified';
|
|
4671
|
+
}
|
|
4672
|
+
return [
|
|
4673
|
+
signature.role,
|
|
4674
|
+
signature.roleGroup,
|
|
4675
|
+
signature.scale,
|
|
4676
|
+
signature.density,
|
|
4677
|
+
signature.surface,
|
|
4678
|
+
signature.layout,
|
|
4679
|
+
signature.flags.join(','),
|
|
4680
|
+
]
|
|
4681
|
+
.filter(Boolean)
|
|
4682
|
+
.join('|');
|
|
4683
|
+
}
|
|
1754
4684
|
function classSignature(classes) {
|
|
1755
4685
|
if (!classes.length) {
|
|
1756
4686
|
return 'no-classes';
|
|
@@ -1771,7 +4701,7 @@ function normalizeTagForSignature(element) {
|
|
|
1771
4701
|
return element.originalTag.toLowerCase();
|
|
1772
4702
|
}
|
|
1773
4703
|
function normalizeChildTagForSignature(child) {
|
|
1774
|
-
if (['button', 'input', 'badge', 'heading', 'text', 'link'].includes(child.kind)) {
|
|
4704
|
+
if (['button', 'input', 'badge', 'heading', 'text', 'link', 'icon', 'image', 'divider'].includes(child.kind)) {
|
|
1775
4705
|
return child.kind;
|
|
1776
4706
|
}
|
|
1777
4707
|
return child.originalTag.toLowerCase();
|
|
@@ -1840,6 +4770,30 @@ function addStyleUsageEdges(entries, files, edges) {
|
|
|
1840
4770
|
}
|
|
1841
4771
|
}
|
|
1842
4772
|
}
|
|
4773
|
+
function addDatabaseUsageEdges(entries, files, edges) {
|
|
4774
|
+
const modelEntries = entries.filter((entry) => entry.group === 'model');
|
|
4775
|
+
const modelKeysByName = new Map();
|
|
4776
|
+
for (const entry of modelEntries) {
|
|
4777
|
+
const name = typeof entry.value.name === 'string' ? entry.value.name : '';
|
|
4778
|
+
const tableName = typeof entry.value.tableName === 'string' ? entry.value.tableName : '';
|
|
4779
|
+
if (name) {
|
|
4780
|
+
modelKeysByName.set(name.toLowerCase(), entry.key);
|
|
4781
|
+
}
|
|
4782
|
+
if (tableName) {
|
|
4783
|
+
modelKeysByName.set(tableName.toLowerCase(), entry.key);
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
for (const file of files) {
|
|
4787
|
+
for (const access of file.databaseAccesses) {
|
|
4788
|
+
const targetName = access.model ?? access.table;
|
|
4789
|
+
const targetKey = targetName ? modelKeysByName.get(targetName.toLowerCase()) : undefined;
|
|
4790
|
+
if (!targetKey) {
|
|
4791
|
+
continue;
|
|
4792
|
+
}
|
|
4793
|
+
edges.push(toGraphEdge(file.key, targetKey, databaseAccessEdgeType(access), `database.${access.framework}.access`, file, access.confidence));
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
1843
4797
|
function addModelUsageEdges(entries, files, edges) {
|
|
1844
4798
|
const modelEntries = entries.filter((entry) => entry.group === 'model');
|
|
1845
4799
|
for (const file of files) {
|
|
@@ -1870,6 +4824,50 @@ function addModelUsageEdges(entries, files, edges) {
|
|
|
1870
4824
|
}
|
|
1871
4825
|
}
|
|
1872
4826
|
}
|
|
4827
|
+
function addDatabaseReviewItems(entries, reviewItems) {
|
|
4828
|
+
const modelEntries = entries.filter((entry) => entry.group === 'model');
|
|
4829
|
+
const byName = (0, utils_1.groupBy)(modelEntries, (entry) => String(entry.value.name ?? entry.key).toLowerCase());
|
|
4830
|
+
for (const [name, candidates] of byName) {
|
|
4831
|
+
if (candidates.length < 2) {
|
|
4832
|
+
continue;
|
|
4833
|
+
}
|
|
4834
|
+
const signatures = (0, utils_1.unique)(candidates.map((candidate) => {
|
|
4835
|
+
const fields = Array.isArray(candidate.value.fields)
|
|
4836
|
+
? candidate.value.fields.map(String).sort()
|
|
4837
|
+
: [];
|
|
4838
|
+
const provider = String(candidate.value.provider ?? '');
|
|
4839
|
+
const tableName = String(candidate.value.tableName ?? '');
|
|
4840
|
+
return `${provider}:${tableName}:${fields.join('|')}`;
|
|
4841
|
+
}));
|
|
4842
|
+
if (signatures.length <= 1) {
|
|
4843
|
+
continue;
|
|
4844
|
+
}
|
|
4845
|
+
reviewItems.push({
|
|
4846
|
+
id: `review.data.model.${name}.conflict`,
|
|
4847
|
+
type: 'concept_collision',
|
|
4848
|
+
status: 'open',
|
|
4849
|
+
group: 'model',
|
|
4850
|
+
s: `Multiple ${name} data model shapes were found. Confirm the canonical model/table before broad generation.`,
|
|
4851
|
+
candidates: candidates.map((candidate) => candidate.key).slice(0, 40),
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4854
|
+
const riskyAccessCandidates = entries
|
|
4855
|
+
.filter((entry) => entry.group === 'database' &&
|
|
4856
|
+
entry.value.kind === 'database-access' &&
|
|
4857
|
+
Array.isArray(entry.value.risk) &&
|
|
4858
|
+
entry.value.risk.includes('manual-review'))
|
|
4859
|
+
.map((entry) => entry.key);
|
|
4860
|
+
if (riskyAccessCandidates.length) {
|
|
4861
|
+
reviewItems.push({
|
|
4862
|
+
id: 'review.data.access.manual-review',
|
|
4863
|
+
type: 'low_confidence',
|
|
4864
|
+
status: 'open',
|
|
4865
|
+
group: 'database',
|
|
4866
|
+
s: 'Raw SQL or high-risk database accesses were found. Review before using them as broad generation patterns.',
|
|
4867
|
+
candidates: riskyAccessCandidates.slice(0, 40),
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
1873
4871
|
function addConceptClusters(entries, usedKeys, nodes, edges, reviewItems) {
|
|
1874
4872
|
const concepts = [
|
|
1875
4873
|
{
|
|
@@ -2082,9 +5080,25 @@ function detectProjectFacts(files) {
|
|
|
2082
5080
|
}
|
|
2083
5081
|
if (dependencies.has('prisma') ||
|
|
2084
5082
|
dependencies.has('@prisma/client') ||
|
|
2085
|
-
files.some((file) => file.
|
|
5083
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'prisma'))) {
|
|
2086
5084
|
frameworks.add('prisma');
|
|
2087
5085
|
}
|
|
5086
|
+
if (dependencies.has('drizzle-orm') ||
|
|
5087
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'drizzle'))) {
|
|
5088
|
+
frameworks.add('drizzle');
|
|
5089
|
+
}
|
|
5090
|
+
if (dependencies.has('typeorm') ||
|
|
5091
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'typeorm'))) {
|
|
5092
|
+
frameworks.add('typeorm');
|
|
5093
|
+
}
|
|
5094
|
+
if (dependencies.has('sequelize') ||
|
|
5095
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'sequelize'))) {
|
|
5096
|
+
frameworks.add('sequelize');
|
|
5097
|
+
}
|
|
5098
|
+
if (dependencies.has('mongoose') ||
|
|
5099
|
+
files.some((file) => file.databaseModels.some((model) => model.provider === 'mongoose'))) {
|
|
5100
|
+
frameworks.add('mongoose');
|
|
5101
|
+
}
|
|
2088
5102
|
if (files.some((file) => file.language === 'css' || file.language === 'scss')) {
|
|
2089
5103
|
frameworks.add('css');
|
|
2090
5104
|
}
|
|
@@ -2119,7 +5133,9 @@ function detectProjectFacts(files) {
|
|
|
2119
5133
|
if (files.some((file) => file.language === 'go')) {
|
|
2120
5134
|
frameworks.add('go');
|
|
2121
5135
|
}
|
|
2122
|
-
if (files.some((file) => file.language === 'sql'
|
|
5136
|
+
if (files.some((file) => file.language === 'sql' ||
|
|
5137
|
+
file.databaseModels.some((model) => model.provider === 'sql') ||
|
|
5138
|
+
file.databaseMigrations.some((migration) => migration.provider === 'sql'))) {
|
|
2123
5139
|
frameworks.add('sql');
|
|
2124
5140
|
}
|
|
2125
5141
|
return {
|
|
@@ -2137,8 +5153,10 @@ function detectFrameworkForFile(file) {
|
|
|
2137
5153
|
if (file.controllers.length || file.services.length || file.modules.length) {
|
|
2138
5154
|
return 'nestjs';
|
|
2139
5155
|
}
|
|
2140
|
-
if (file.
|
|
2141
|
-
return
|
|
5156
|
+
if (file.databaseModels.length || file.databaseEnums.length || file.databaseMigrations.length) {
|
|
5157
|
+
return (file.databaseModels[0]?.provider ??
|
|
5158
|
+
file.databaseEnums[0]?.provider ??
|
|
5159
|
+
file.databaseMigrations[0]?.provider);
|
|
2142
5160
|
}
|
|
2143
5161
|
if (file.kind.includes('style') || file.kind.includes('css')) {
|
|
2144
5162
|
return 'css';
|