@shapeshift-labs/frontier-lang-css 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -36,6 +36,20 @@ export interface CssSourceSpan {
36
36
  readonly endColumn: number;
37
37
  }
38
38
 
39
+ export interface CssParserDiagnostic {
40
+ readonly reason?: string;
41
+ readonly line?: number;
42
+ readonly column?: number;
43
+ readonly input?: string;
44
+ readonly [key: string]: unknown;
45
+ }
46
+
47
+ export interface CssParserEvidence {
48
+ readonly name: 'postcss' | string;
49
+ readonly sourceCodeLocationInfo: boolean;
50
+ readonly parseErrors: readonly CssParserDiagnostic[];
51
+ }
52
+
39
53
  export interface CssSourceRef {
40
54
  readonly semanticNodeId: string;
41
55
  readonly semanticNodeKind?: string;
@@ -109,6 +123,8 @@ export interface CssSemanticDeclaration {
109
123
  readonly value: string;
110
124
  readonly important: boolean;
111
125
  readonly valueHash: string;
126
+ readonly sourceSpan?: CssSourceSpan;
127
+ readonly rawTextHash?: string;
112
128
  readonly ordinal: number;
113
129
  readonly cascadeKey: string;
114
130
  readonly declarationHash: string;
@@ -129,6 +145,8 @@ export interface CssSemanticRecord {
129
145
  readonly scopedCascadeGraphHash?: string;
130
146
  readonly sourceSpan: CssSourceSpan;
131
147
  readonly sourceHash: string;
148
+ readonly parser?: 'postcss' | string;
149
+ readonly rawTextHash?: string;
132
150
  readonly ruleHash?: string;
133
151
  readonly atRuleHash?: string;
134
152
  readonly proofGaps?: readonly CssSemanticProofGap[];
@@ -200,6 +218,7 @@ export interface CssSemanticSheet {
200
218
  readonly sheetHash: string;
201
219
  readonly summary: Readonly<Record<string, number>>;
202
220
  readonly proofGaps: readonly CssSemanticProofGap[];
221
+ readonly parser: CssParserEvidence;
203
222
  }
204
223
 
205
224
  export interface CssSemanticMergeEvidence {
@@ -254,40 +273,35 @@ export interface CssSafeMergeResult {
254
273
  readonly headChangedDeclarations?: number;
255
274
  readonly workerChangedCssModuleContracts?: number;
256
275
  readonly headChangedCssModuleContracts?: number;
276
+ readonly parserEvidence?: CssSafeMergeParserEvidence;
277
+ }
278
+
279
+ export interface CssSafeMergeParserEvidence {
280
+ readonly kind: 'frontier.lang.cssSafeMergeParserEvidence';
281
+ readonly version: 1;
282
+ readonly parserNames: readonly string[];
283
+ readonly sourceCodeLocationInfo: boolean; readonly parserBackedSourceSpans: boolean; readonly parserBackedDeclarationSpans: boolean; readonly parserBackedTriviaHashes: boolean;
284
+ readonly scopedCascadeGraphHashPresent: boolean; readonly parseErrors: number;
285
+ readonly sides: Readonly<Record<string, CssSafeMergeParserSideEvidence>>;
286
+ }
287
+
288
+ export interface CssSafeMergeParserSideEvidence {
289
+ readonly parserName: string;
290
+ readonly sourceCodeLocationInfo: boolean; readonly parserBackedSourceSpans: boolean; readonly parserBackedDeclarationSpans: boolean; readonly parserBackedTriviaHashes: boolean;
291
+ readonly scopedCascadeGraphHashPresent: boolean; readonly parseErrors: number; readonly recordCount: number; readonly declarationCount: number;
257
292
  }
258
293
 
259
294
  export interface CssSafeMergeInput {
260
- readonly id?: string;
261
- readonly sourcePath?: string;
262
- readonly baseSourceText?: string;
263
- readonly workerSourceText?: string;
264
- readonly headSourceText?: string;
265
- readonly cssModule?: boolean;
266
- readonly cssModules?: boolean;
295
+ readonly id?: string; readonly sourcePath?: string; readonly baseSourceText?: string; readonly workerSourceText?: string; readonly headSourceText?: string;
296
+ readonly cssModule?: boolean; readonly cssModules?: boolean;
267
297
  readonly generatedClassNameMap?: Readonly<Record<string, string>>;
268
- readonly generatedClassNameMapHash?: string;
269
- readonly jsTsUseSiteGraphHash?: string;
270
- readonly cssModuleCompositionGraphHash?: string;
271
- readonly icssGraphHash?: string;
272
- readonly scopedCascadeGraphHash?: string;
273
- readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>;
274
- readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>;
275
- readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
276
- readonly baseGeneratedClassNameMapHash?: string;
277
- readonly workerGeneratedClassNameMapHash?: string;
278
- readonly headGeneratedClassNameMapHash?: string;
279
- readonly baseJsTsUseSiteGraphHash?: string;
280
- readonly workerJsTsUseSiteGraphHash?: string;
281
- readonly headJsTsUseSiteGraphHash?: string;
282
- readonly baseCssModuleCompositionGraphHash?: string;
283
- readonly workerCssModuleCompositionGraphHash?: string;
284
- readonly headCssModuleCompositionGraphHash?: string;
285
- readonly baseIcssGraphHash?: string;
286
- readonly workerIcssGraphHash?: string;
287
- readonly headIcssGraphHash?: string;
288
- readonly baseScopedCascadeGraphHash?: string;
289
- readonly workerScopedCascadeGraphHash?: string;
290
- readonly headScopedCascadeGraphHash?: string;
298
+ readonly generatedClassNameMapHash?: string; readonly jsTsUseSiteGraphHash?: string; readonly cssModuleCompositionGraphHash?: string; readonly icssGraphHash?: string; readonly scopedCascadeGraphHash?: string;
299
+ readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
300
+ readonly baseGeneratedClassNameMapHash?: string; readonly workerGeneratedClassNameMapHash?: string; readonly headGeneratedClassNameMapHash?: string;
301
+ readonly baseJsTsUseSiteGraphHash?: string; readonly workerJsTsUseSiteGraphHash?: string; readonly headJsTsUseSiteGraphHash?: string;
302
+ readonly baseCssModuleCompositionGraphHash?: string; readonly workerCssModuleCompositionGraphHash?: string; readonly headCssModuleCompositionGraphHash?: string;
303
+ readonly baseIcssGraphHash?: string; readonly workerIcssGraphHash?: string; readonly headIcssGraphHash?: string;
304
+ readonly baseScopedCascadeGraphHash?: string; readonly workerScopedCascadeGraphHash?: string; readonly headScopedCascadeGraphHash?: string;
291
305
  }
292
306
 
293
307
  export declare function toCssAst(document: FrontierLangDocument, options?: CssProjectionOptions): CssAstStylesheet;
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
2
  import { createCssModuleEvidence } from './css-modules.js';
3
+ import { parsePostcssSemanticRecords } from './postcss-parser-evidence.js';
3
4
  import { safeMergeCssSource as safeMergeCssSourceImpl } from './semantic-merge.js';
4
5
 
5
6
  const ShorthandProperties = new Set(['all', 'animation', 'background', 'border', 'border-block', 'border-color', 'border-image', 'border-inline', 'border-radius', 'border-style', 'border-width', 'columns', 'flex', 'font', 'gap', 'grid', 'grid-area', 'grid-column', 'grid-row', 'inset', 'list-style', 'margin', 'offset', 'outline', 'overflow', 'padding', 'place-content', 'place-items', 'place-self', 'text-decoration', 'transition']);
@@ -57,11 +58,12 @@ export function emitCssWithSourceMap(document, options = {}) {
57
58
  }
58
59
 
59
60
  export function parseCssSemanticSheet(sourceText, options = {}) {
60
- const lineStarts = computeLineStarts(sourceText);
61
61
  const sourceHash = options.sourceHash ?? hashSemanticValue({ kind: 'frontier.lang.css.source.v1', sourceText });
62
- const records = parseCssBlocks(sourceText, 0, sourceText.length, [], lineStarts, sourceHash, options);
62
+ const parsed = parsePostcssSemanticRecords(sourceText, sourceHash, options);
63
+ const records = parsed.records;
63
64
  const cssModules = createCssModuleEvidence(records, options, sourceHash);
64
65
  const proofGaps = [
66
+ ...parsed.proofGaps,
65
67
  ...records.flatMap((record) => record.proofGaps ?? []),
66
68
  ...(cssModules?.proofGaps ?? [])
67
69
  ];
@@ -81,9 +83,11 @@ export function parseCssSemanticSheet(sourceText, options = {}) {
81
83
  cssModuleCompositions: cssModules?.compositions.length ?? 0,
82
84
  icssImports: cssModules?.icssImports.length ?? 0,
83
85
  icssExports: cssModules?.icssExports.length ?? 0,
84
- proofGaps: proofGaps.length
86
+ proofGaps: proofGaps.length,
87
+ parseErrors: parsed.parser.parseErrors.length
85
88
  },
86
- proofGaps
89
+ proofGaps,
90
+ parser: parsed.parser
87
91
  };
88
92
  }
89
93
 
@@ -0,0 +1,151 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import postcss from 'postcss';
3
+
4
+ const ShorthandProperties = new Set(['all', 'animation', 'background', 'border', 'border-block', 'border-color', 'border-image', 'border-inline', 'border-radius', 'border-style', 'border-width', 'columns', 'flex', 'font', 'gap', 'grid', 'grid-area', 'grid-column', 'grid-row', 'inset', 'list-style', 'margin', 'offset', 'outline', 'overflow', 'padding', 'place-content', 'place-items', 'place-self', 'text-decoration', 'transition']);
5
+ const RuntimeAtRules = new Set(['keyframes', 'font-face', 'page', 'property']);
6
+ const ScopeAtRules = new Set(['media', 'supports', 'container', 'layer', 'scope']);
7
+
8
+ function parsePostcssSemanticRecords(sourceText, sourceHash, options) {
9
+ try {
10
+ const root = postcss.parse(sourceText, { from: options.sourcePath });
11
+ const records = postcssContainerRecords(root.nodes ?? [], [], sourceHash, options);
12
+ return { records, proofGaps: [], parser: { name: 'postcss', sourceCodeLocationInfo: true, parseErrors: [] } };
13
+ } catch (error) {
14
+ const reason = error?.reason ?? error?.message ?? 'CSS parser failed';
15
+ return {
16
+ records: [],
17
+ proofGaps: [proofGap('css-parser-error', `CSS parser rejected source: ${reason}`)],
18
+ parser: {
19
+ name: 'postcss',
20
+ sourceCodeLocationInfo: true,
21
+ parseErrors: [compactRecord({ reason, line: error?.line, column: error?.column, input: error?.input?.file })]
22
+ }
23
+ };
24
+ }
25
+ }
26
+
27
+ function postcssContainerRecords(nodes, scopes, sourceHash, options) {
28
+ const records = [];
29
+ for (const node of nodes) {
30
+ if (node.type === 'rule') records.push(postcssRuleRecord(node, scopes, sourceHash, options));
31
+ else if (node.type === 'atrule') {
32
+ records.push(postcssAtRuleRecord(node, scopes, sourceHash, options));
33
+ if (ScopeAtRules.has(String(node.name).toLowerCase()) && node.nodes?.length) {
34
+ records.push(...postcssContainerRecords(node.nodes, [...scopes, postcssAtRuleScopeKey(node)], sourceHash, options));
35
+ }
36
+ }
37
+ }
38
+ return records.sort((left, right) => left.sourceSpan.startOffset - right.sourceSpan.startOffset);
39
+ }
40
+
41
+ function postcssRuleRecord(node, scopes, sourceHash, options) {
42
+ const selectors = String(node.selector ?? '').split(',').map((selector) => selector.trim()).filter(Boolean);
43
+ const declarations = (node.nodes ?? []).filter((child) => child.type === 'decl').map(postcssDeclaration);
44
+ const nestedChildren = (node.nodes ?? []).filter((child) => child.type !== 'decl' && child.type !== 'comment');
45
+ const proofGaps = [
46
+ ...declarations.filter((declaration) => ShorthandProperties.has(declaration.property)).map((declaration) => proofGap('css-shorthand-expansion-unproved', `CSS shorthand ${declaration.property} needs longhand expansion evidence.`)),
47
+ ...scopes.length && !options.scopedCascadeGraphHash ? [proofGap('css-scoped-cascade-equivalence-unproved', 'Scoped cascade equivalence requires browser/style evidence.')] : [],
48
+ ...nestedChildren.length ? [proofGap('css-nesting-semantic-unproved', 'CSS nested rule semantics require nesting expansion evidence.')] : []
49
+ ];
50
+ return compactRecord({
51
+ kind: 'rule',
52
+ selectors,
53
+ selectorHash: hashSemanticValue({ kind: 'frontier.lang.css.selectors.v2.postcss', selectors }),
54
+ specificity: selectors.map(selectorSpecificity),
55
+ scopes,
56
+ declarations: declarations.map((declaration, ordinal) => ({
57
+ ...declaration,
58
+ ordinal,
59
+ cascadeKey: [...scopes, selectors.join(','), declaration.property].join('::'),
60
+ declarationHash: hashSemanticValue({ kind: 'frontier.lang.css.declaration.v2.postcss', scopes, selectors, property: declaration.property, rawProperty: declaration.rawProperty, value: declaration.value, important: declaration.important })
61
+ })),
62
+ customProperties: declarations.filter((declaration) => declaration.property.startsWith('--')).map((declaration) => declaration.property),
63
+ scopedCascadeGraphHash: scopes.length ? options.scopedCascadeGraphHash : undefined,
64
+ sourceSpan: sourceSpanFromPostcss(node.source, options.sourcePath),
65
+ sourceHash,
66
+ rawTextHash: hashSemanticValue({ kind: 'frontier.lang.css.rawRuleText.v1', text: node.toString() }),
67
+ ruleHash: hashSemanticValue({ kind: 'frontier.lang.css.rule.v2.postcss', selectors, scopes, declarations, nestedChildren: nestedChildren.map((child) => child.type) }),
68
+ parser: 'postcss',
69
+ proofGaps: proofGaps.length ? proofGaps : undefined
70
+ });
71
+ }
72
+
73
+ function postcssDeclaration(node) {
74
+ const property = String(node.prop ?? '').toLowerCase();
75
+ const value = String(node.value ?? '').trim();
76
+ return {
77
+ property,
78
+ rawProperty: node.raws?.prop?.raw ?? node.prop,
79
+ value,
80
+ important: node.important === true,
81
+ valueHash: hashSemanticValue({ kind: 'frontier.lang.css.value.v2.postcss', value }),
82
+ sourceSpan: sourceSpanFromPostcss(node.source, node.source?.input?.file),
83
+ rawTextHash: hashSemanticValue({ kind: 'frontier.lang.css.rawDeclarationText.v1', text: node.toString() })
84
+ };
85
+ }
86
+
87
+ function postcssAtRuleRecord(node, scopes, sourceHash, options) {
88
+ const atRuleName = String(node.name ?? 'unknown').toLowerCase();
89
+ const conditionText = String(node.params ?? '').trim();
90
+ const rawText = rawPostcssText(node);
91
+ const proofGaps = [];
92
+ if (RuntimeAtRules.has(atRuleName)) proofGaps.push(proofGap(`css-${atRuleName}-runtime-equivalence-unproved`, `CSS @${atRuleName} semantics require browser evidence.`));
93
+ if (ScopeAtRules.has(atRuleName) && !options.scopedCascadeGraphHash) proofGaps.push(proofGap(`css-${atRuleName}-cascade-scope-unproved`, `CSS @${atRuleName} scoped cascade requires condition evaluation evidence.`));
94
+ if (!node.nodes?.length && atRuleName === 'layer') proofGaps.push(proofGap('css-layer-order-statement-unsupported', 'CSS @layer statement order requires cascade order evidence.'));
95
+ else if (!node.nodes?.length) proofGaps.push(proofGap(`css-${atRuleName}-statement-equivalence-unproved`, `CSS @${atRuleName} statement semantics require host evidence.`));
96
+ const kind = node.nodes?.length ? 'at-rule' : 'at-rule-statement';
97
+ return compactRecord({
98
+ kind,
99
+ atRuleName,
100
+ conditionText,
101
+ statementText: kind === 'at-rule-statement' ? rawText : undefined,
102
+ scopeKey: postcssAtRuleScopeKey(node),
103
+ scopes,
104
+ scopedCascadeGraphHash: ScopeAtRules.has(atRuleName) ? options.scopedCascadeGraphHash : undefined,
105
+ sourceSpan: sourceSpanFromPostcss(node.source, options.sourcePath),
106
+ sourceHash,
107
+ rawTextHash: hashSemanticValue({ kind: 'frontier.lang.css.rawAtRuleText.v1', text: rawText }),
108
+ atRuleHash: hashSemanticValue({ kind: 'frontier.lang.css.atRule.v2.postcss', atRuleName, conditionText, scopes, statementText: kind === 'at-rule-statement' ? rawText : undefined }),
109
+ parser: 'postcss',
110
+ proofGaps: proofGaps.length ? proofGaps : undefined
111
+ });
112
+ }
113
+
114
+ function postcssAtRuleScopeKey(node) {
115
+ return `@${String(node.name ?? 'unknown').toLowerCase()} ${String(node.params ?? '').trim()}`.trim();
116
+ }
117
+
118
+ function sourceSpanFromPostcss(source, fallbackPath) {
119
+ const start = source?.start;
120
+ const end = source?.end;
121
+ if (!start || !end) return undefined;
122
+ return compactRecord({
123
+ path: fallbackPath,
124
+ startOffset: start.offset,
125
+ endOffset: end.offset,
126
+ startLine: start.line,
127
+ startColumn: start.column,
128
+ endLine: end.line,
129
+ endColumn: end.column
130
+ });
131
+ }
132
+
133
+ function rawPostcssText(node) {
134
+ const css = node.source?.input?.css;
135
+ const start = node.source?.start?.offset;
136
+ const end = node.source?.end?.offset;
137
+ return typeof css === 'string' && Number.isFinite(start) && Number.isFinite(end) ? css.slice(start, end) : node.toString();
138
+ }
139
+
140
+ function selectorSpecificity(selector) {
141
+ const withoutStrings = selector.replace(/"[^"]*"|'[^']*'/g, '');
142
+ const ids = (withoutStrings.match(/#[\w-]+/g) ?? []).length;
143
+ const classes = (withoutStrings.match(/\.[\w-]+|\[[^\]]+\]|:(?!:)[\w-]+(?:\([^)]*\))?/g) ?? []).length;
144
+ const elements = (withoutStrings.replace(/#[\w-]+|\.[\w-]+|\[[^\]]+\]|:{1,2}[\w-]+(?:\([^)]*\))?/g, ' ').match(/\b[A-Za-z][\w-]*\b/g) ?? []).length;
145
+ return [ids, classes, elements];
146
+ }
147
+
148
+ function proofGap(code, summary) { return { code, status: 'not-claimed', summary, failClosed: true, semanticEquivalenceClaim: false }; }
149
+ function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
150
+
151
+ export { parsePostcssSemanticRecords };
@@ -22,14 +22,16 @@ function safeMergeCssSource(input = {}, context = {}) {
22
22
  };
23
23
  const moduleChanges = cssModuleContractChanges(sheets, hash);
24
24
  const proofConflicts = proofGapConflicts(id, sourcePath, changed, indexes);
25
+ const parserConflicts = parserErrorConflicts(id, sourcePath, sheets);
25
26
  const overlapConflicts = [
26
27
  ...overlapDeclarationConflicts(id, sourcePath, changed.worker, changed.head),
27
28
  ...shorthandOverlapConflicts(id, sourcePath, changed.worker, changed.head)
28
29
  ];
29
30
  const moduleConflicts = cssModuleContractConflicts(id, sourcePath, moduleChanges);
30
31
  const sourceShapeConflicts = unsupportedSourceShapeConflicts(id, sourcePath, sheets, changed, hash);
31
- const conflicts = [...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...sourceShapeConflicts];
32
- if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts);
32
+ const conflicts = [...parserConflicts, ...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...sourceShapeConflicts];
33
+ const parserEvidence = mergeParserEvidence(sheets);
34
+ if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence });
33
35
  const mergedIndex = applyDeclarationChanges(applyDeclarationChanges(indexes.base, changed.head), changed.worker);
34
36
  return merged(id, sourcePath, renderDeclarationIndex(mergedIndex), 'semantic-declaration-merge', hash, {
35
37
  baseSheetHash: sheets.base.sheetHash,
@@ -38,7 +40,8 @@ function safeMergeCssSource(input = {}, context = {}) {
38
40
  workerChangedDeclarations: changed.worker.length,
39
41
  headChangedDeclarations: changed.head.length,
40
42
  workerChangedCssModuleContracts: moduleChanges.worker.length,
41
- headChangedCssModuleContracts: moduleChanges.head.length
43
+ headChangedCssModuleContracts: moduleChanges.head.length,
44
+ parserEvidence
42
45
  });
43
46
  }
44
47
 
@@ -100,6 +103,44 @@ function proofGapConflicts(id, sourcePath, changed, indexes) {
100
103
  });
101
104
  }
102
105
 
106
+ function parserErrorConflicts(id, sourcePath, sheets) {
107
+ return Object.entries(sheets).flatMap(([side, sheet]) => (sheet.proofGaps ?? [])
108
+ .filter((gap) => gap.code === 'css-parser-error')
109
+ .map((gap) => conflict(id, sourcePath, 'css-parser-error-blocked', gap.code, { side, proofGap: gap })));
110
+ }
111
+
112
+ function mergeParserEvidence(sheets) {
113
+ const entries = Object.entries(sheets).map(([side, sheet]) => [side, sheetParserEvidence(sheet)]);
114
+ return {
115
+ kind: 'frontier.lang.cssSafeMergeParserEvidence',
116
+ version: 1,
117
+ parserNames: unique(entries.map(([, evidence]) => evidence.parserName)),
118
+ sourceCodeLocationInfo: entries.every(([, evidence]) => evidence.sourceCodeLocationInfo === true),
119
+ parserBackedSourceSpans: entries.every(([, evidence]) => evidence.parserBackedSourceSpans === true),
120
+ parserBackedDeclarationSpans: entries.every(([, evidence]) => evidence.parserBackedDeclarationSpans === true),
121
+ parserBackedTriviaHashes: entries.every(([, evidence]) => evidence.parserBackedTriviaHashes === true),
122
+ scopedCascadeGraphHashPresent: entries.every(([, evidence]) => evidence.scopedCascadeGraphHashPresent === true),
123
+ parseErrors: entries.reduce((sum, [, evidence]) => sum + evidence.parseErrors, 0),
124
+ sides: Object.fromEntries(entries)
125
+ };
126
+ }
127
+
128
+ function sheetParserEvidence(sheet) {
129
+ const records = sheet.records ?? [];
130
+ const declarations = records.flatMap((record) => record.declarations ?? []);
131
+ return {
132
+ parserName: sheet.parser?.name ?? 'unknown',
133
+ sourceCodeLocationInfo: sheet.parser?.sourceCodeLocationInfo === true,
134
+ parserBackedSourceSpans: records.some((record) => record.parser === 'postcss' && record.sourceSpan?.startOffset !== undefined),
135
+ parserBackedDeclarationSpans: declarations.some((declaration) => declaration.sourceSpan?.startOffset !== undefined),
136
+ parserBackedTriviaHashes: records.some((record) => record.parser === 'postcss' && typeof record.rawTextHash === 'string'),
137
+ scopedCascadeGraphHashPresent: records.every((record) => !(record.scopes?.length) || Boolean(record.scopedCascadeGraphHash)),
138
+ parseErrors: sheet.parser?.parseErrors?.length ?? 0,
139
+ recordCount: records.length,
140
+ declarationCount: declarations.length
141
+ };
142
+ }
143
+
103
144
  function overlapDeclarationConflicts(id, sourcePath, workerChanges, headChanges) {
104
145
  const headByKey = new Map(headChanges.map((change) => [change.key, change]));
105
146
  return workerChanges.flatMap((workerChange) => {
@@ -210,10 +251,11 @@ function merged(id, sourcePath, sourceText, operation, hash, extra = {}) {
210
251
  });
211
252
  }
212
253
 
213
- function blocked(id, sourcePath, reasonCode, conflicts = []) {
254
+ function blocked(id, sourcePath, reasonCode, conflicts = [], extra = {}) {
214
255
  return result(id, sourcePath, 'blocked', {
215
256
  operation: 'blocked',
216
- conflicts: conflicts.length ? conflicts : [conflict(id, sourcePath, reasonCode, reasonCode)]
257
+ conflicts: conflicts.length ? conflicts : [conflict(id, sourcePath, reasonCode, reasonCode)],
258
+ ...extra
217
259
  });
218
260
  }
219
261
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-css",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "CSS semantic merge evidence and projection adapter for Frontier Lang semantic source documents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,7 +53,8 @@
53
53
  "access": "public"
54
54
  },
55
55
  "dependencies": {
56
- "@shapeshift-labs/frontier-lang-kernel": "0.3.12"
56
+ "@shapeshift-labs/frontier-lang-kernel": "0.3.12",
57
+ "postcss": "^8.5.15"
57
58
  },
58
59
  "devDependencies": {
59
60
  "typescript": "^5.9.3"