@shapeshift-labs/frontier-lang-css 0.1.16 → 0.1.18

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 CHANGED
@@ -232,11 +232,11 @@ const merge = safeMergeCssSource({
232
232
  });
233
233
  ```
234
234
 
235
- `sourceMap.mappings` links emitted rule blocks back to Frontier Lang semantic node ids. `createCssSemanticMergeEvidence` records selectors, specificity, declarations, custom properties, `var()` fallback references, animation/keyframe links, font-face links, URL asset references, cascade keys, statement-form at-rules, block-form runtime at-rules, CSS Modules exports, ICSS edges, scoped cascade graph proof hashes, source spans, stable hashes, and fail-closed proof gaps for cascade/render-sensitive CSS surfaces. `safeMergeCssSource` admits independent declaration edits by cascade key, including existing scoped `@media` / `@supports` / `@container` / `@layer` declarations when a scoped cascade graph proof hash is supplied, carries source-bound dependency graph evidence into the merge result, blocks duplicate ordered occurrences of the same cascade key until ordered cascade evidence exists, blocks dependency-affecting declaration edits unless an exact source-bound dependency graph proof is supplied, blocks selector-target rebases unless the host supplies a source-bound selector target graph hash and the parser proves selector specificity is invariant, blocks parallel edits whose CSS shorthand expansions overlap a longhand or sub-shorthand, preserves unchanged statement-form at-rules such as `@layer reset, components;`, preserves unchanged block-form runtime at-rules such as `@keyframes` and `@font-face`, and for `.module.css` files it classifies exported local classes, `composes`, and ICSS import/export records as explicit merge contracts. Cascade-sensitive source-shape changes still block by default, but a host can attach a `css-source-bound-cascade-runtime-proof` / `css-cascade-runtime-proof` that is bound to the exact source path, reason, side, shape key, and base/worker/head/output source hashes to admit that specific change.
235
+ `sourceMap.mappings` links emitted rule blocks back to Frontier Lang semantic node ids. `createCssSemanticMergeEvidence` records selectors, specificity, declarations, custom properties, `var()` fallback references, animation/keyframe links, font-face links, URL asset references, cascade keys, statement-form at-rules, block-form runtime at-rules, CSS Modules exports, ICSS edges, scoped cascade graph proof hashes, source spans, stable hashes, and fail-closed proof gaps for cascade/render-sensitive CSS surfaces. `safeMergeCssSource` admits independent declaration edits by cascade key, including existing scoped `@media` / `@supports` / `@container` / `@layer` declarations when a scoped cascade graph proof hash is supplied, carries deterministic box-shorthand expansion evidence for supported margin/padding/inset/gap/scroll/border-edge families, carries source-bound dependency graph evidence into the merge result, blocks duplicate ordered occurrences of the same cascade key until ordered cascade evidence exists, blocks dependency-affecting declaration edits unless an exact source-bound dependency graph proof is supplied, blocks selector-target rebases unless the host supplies a source-bound selector target graph hash and the parser proves selector specificity is invariant, blocks parallel edits whose CSS shorthand expansions overlap a longhand or sub-shorthand, preserves unchanged statement-form at-rules such as `@layer reset, components;`, preserves unchanged block-form runtime at-rules such as `@keyframes` and `@font-face`, and for `.module.css` files it classifies exported local classes, `composes`, and ICSS import/export records as explicit merge contracts. Cascade-sensitive source-shape changes still block by default, but a host can attach a `css-source-bound-cascade-runtime-proof` / `css-cascade-runtime-proof` that is bound to the exact source path, reason, side, shape key, and base/worker/head/output source hashes to admit that specific change.
236
236
 
237
237
  ## Support Boundary
238
238
 
239
239
  - Ready evidence: style rules, selectors, specificity, declarations, source-bound dependency graph hashes for custom properties, `var()` fallbacks, animations/keyframes, font faces, and URL assets, statement-form at-rules, block-form runtime at-rules, CSS Modules local exports, generated class-name map coverage, JS/TS use-site graph hashes, composition graph hashes, ICSS graph hashes, scoped cascade graph hashes, source-bound cascade runtime proofs, source spans, stable hashes.
240
- - Safe merge: independent declarations with non-overlapping cascade keys, no duplicate ordered occurrences for any changed cascade key, non-overlapping shorthand expansion sets, and no changed dependency graph surface; unchanged statement-form at-rules and unchanged block-form runtime at-rules preserved in canonical output; existing scoped declaration edits when scoped cascade graph proof is supplied; dependency-affecting declaration edits only when an exact source-bound dependency graph proof is supplied; selector-target rebases only when a matching selector target graph hash is supplied and before/after selector specificity is identical; source-shape/cascade-sensitive edits only when an exact source-bound cascade runtime proof is supplied; explicit CSS Modules export additions/deletions when generated class-name and JS/TS use-site graph proof is supplied; composition edits when composition graph proof is supplied; ICSS edits when ICSS graph proof is supplied. Output is a canonical CSS render and not a byte/trivia-preserving claim.
241
- - Review-only gaps: duplicate ordered occurrences of the same cascade key, incomplete generated class-name maps, unproved dependency graph changes, unproved CSS Modules use-site graphs, unproved composition or ICSS graphs, shorthand value expansion/equivalence beyond known affected-property overlap checks, statement-form at-rule order/condition changes, one-sided or structurally changed scoped cascade under `@media` / `@supports` / `@container` / `@layer`, changed runtime blocks such as `@keyframes`, `@font-face`, `@page`, and `@property`, browser layout and render equivalence.
240
+ - Safe merge: independent declarations with non-overlapping cascade keys, no duplicate ordered occurrences for any changed cascade key, deterministic box-shorthand expansion evidence for supported shorthand families, non-overlapping shorthand expansion sets, and no changed dependency graph surface; unchanged statement-form at-rules and unchanged block-form runtime at-rules preserved in canonical output; existing scoped declaration edits when scoped cascade graph proof is supplied; dependency-affecting declaration edits only when an exact source-bound dependency graph proof is supplied; selector-target rebases only when a matching selector target graph hash is supplied and before/after selector specificity is identical; source-shape/cascade-sensitive edits only when an exact source-bound cascade runtime proof is supplied; explicit CSS Modules export additions/deletions when generated class-name and JS/TS use-site graph proof is supplied; composition edits when composition graph proof is supplied; ICSS edits when ICSS graph proof is supplied. Output is a canonical CSS render and not a byte/trivia-preserving claim.
241
+ - Review-only gaps: duplicate ordered occurrences of the same cascade key, incomplete generated class-name maps, unproved dependency graph changes, unproved CSS Modules use-site graphs, unproved composition or ICSS graphs, ambiguous shorthand value expansion/equivalence such as `font`, layered `background`, and runtime-substituted `var()` / `env()` shorthand values, statement-form at-rule order/condition changes, one-sided or structurally changed scoped cascade under `@media` / `@supports` / `@container` / `@layer`, changed runtime blocks such as `@keyframes`, `@font-face`, `@page`, and `@property`, browser layout and render equivalence.
242
242
  - Claims: dependency graph evidence is an admission/review signal only unless an exact source-bound dependency graph proof admits the changed dependency surface; `autoMergeClaim`, `semanticEquivalenceClaim`, and `browserRenderEquivalenceClaim` remain false. `browserCascadeEquivalenceClaim` is only true on a safe-merge result when a source-bound cascade runtime proof admits the specific cascade-sensitive source-shape change.
@@ -0,0 +1,57 @@
1
+ export interface CssModuleContractProof {
2
+ readonly id?: string;
3
+ readonly kind: 'css-source-bound-module-contract-proof' | 'css-module-contract-proof' | 'css-source-bound-css-module-contract-proof' | string;
4
+ readonly status: 'passed' | string;
5
+ readonly sourcePath: string;
6
+ readonly side?: string;
7
+ readonly sides?: readonly string[];
8
+ readonly changeKind?: string;
9
+ readonly changeKinds?: readonly string[];
10
+ readonly contractKey?: string;
11
+ readonly contractKeys?: readonly string[];
12
+ readonly contractKind?: string;
13
+ readonly contractKinds?: readonly string[];
14
+ readonly baseSourceText?: string;
15
+ readonly workerSourceText?: string;
16
+ readonly headSourceText?: string;
17
+ readonly outputSourceText?: string;
18
+ readonly mergedSourceText?: string;
19
+ readonly baseSourceHash?: string;
20
+ readonly workerSourceHash?: string;
21
+ readonly headSourceHash?: string;
22
+ readonly outputSourceHash?: string;
23
+ readonly mergedSourceHash?: string;
24
+ readonly sourceTexts?: Readonly<Record<string,string>>;
25
+ readonly sourceHashes?: Readonly<Record<string,string>>;
26
+ readonly sources?: Readonly<Record<string,string>>;
27
+ readonly hashes?: Readonly<Record<string,string>>;
28
+ readonly moduleHash?: string;
29
+ readonly generatedClassNameMapHash?: string;
30
+ readonly jsTsUseSiteGraphHash?: string;
31
+ readonly cssModuleCompositionGraphHash?: string;
32
+ readonly icssGraphHash?: string;
33
+ readonly contractGraphHashes?: Readonly<Record<string,string>>;
34
+ readonly cssModuleGraphHashes?: Readonly<Record<string,string>>;
35
+ readonly proofLevel?: string;
36
+ }
37
+
38
+ export interface CssModuleContractProofRecord {
39
+ readonly id?: string;
40
+ readonly kind: string;
41
+ readonly status: 'passed';
42
+ readonly proofLevel: string;
43
+ readonly sourcePath?: string;
44
+ readonly side: string;
45
+ readonly changeKind: string;
46
+ readonly contractKey: string;
47
+ readonly contractKind: string;
48
+ readonly baseSourceHash?: string;
49
+ readonly workerSourceHash?: string;
50
+ readonly headSourceHash?: string;
51
+ readonly outputSourceHash?: string;
52
+ readonly moduleHash?: string;
53
+ readonly generatedClassNameMapHash?: string;
54
+ readonly jsTsUseSiteGraphHash?: string;
55
+ readonly cssModuleCompositionGraphHash?: string;
56
+ readonly icssGraphHash?: string;
57
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FrontierLangDocument } from '@shapeshift-labs/frontier-lang-kernel'; import type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.js'; import type { CssDependencyGraphEvidence, CssDependencyGraphProof, CssDependencyGraphProofRecord } from './dependency-graph.js'; export type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.js'; export type { CssDependencyGraphChange, CssDependencyGraphEvidence, CssDependencyGraphProof, CssDependencyGraphProofRecord } from './dependency-graph.js';
1
+ import type { FrontierLangDocument } from '@shapeshift-labs/frontier-lang-kernel'; import type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.js'; import type { CssDependencyGraphEvidence, CssDependencyGraphProof, CssDependencyGraphProofRecord } from './dependency-graph.js'; import type { CssModuleContractProof, CssModuleContractProofRecord } from './css-module-contract-proof.js'; import type { CssSafeMergeShorthandExpansionEvidence, CssShorthandExpansionEvidence } from './shorthand-expansion.js'; export type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.js'; export type { CssDependencyGraphChange, CssDependencyGraphEvidence, CssDependencyGraphProof, CssDependencyGraphProofRecord } from './dependency-graph.js'; export type { CssModuleContractProof, CssModuleContractProofRecord } from './css-module-contract-proof.js'; export type { CssSafeMergeChangedShorthandExpansion, CssSafeMergeShorthandExpansionEvidence, CssSafeMergeShorthandExpansionSideEvidence, CssShorthandExpansionEvidence, CssShorthandLonghandExpansion } from './shorthand-expansion.js';
2
2
 
3
3
  export interface CssProjectionOptions {
4
4
  readonly banner?: string;
@@ -111,6 +111,7 @@ export interface CssSemanticDeclaration {
111
111
  readonly ordinal: number;
112
112
  readonly cascadeKey: string;
113
113
  readonly declarationHash: string;
114
+ readonly shorthandExpansion?: CssShorthandExpansionEvidence;
114
115
  }
115
116
 
116
117
  export interface CssSemanticRecord {
@@ -228,6 +229,7 @@ export interface CssSafeMergeAdmission {
228
229
  readonly browserCascadeEquivalenceClaim?: true;
229
230
  readonly cssCascadeRuntimeProofs?: readonly CssCascadeRuntimeProofRecord[];
230
231
  readonly cssDependencyGraphProofs?: readonly CssDependencyGraphProofRecord[];
232
+ readonly cssModuleContractProofs?: readonly CssModuleContractProofRecord[];
231
233
  }
232
234
 
233
235
  export interface CssSafeMergeResult {
@@ -241,7 +243,8 @@ export interface CssSafeMergeResult {
241
243
  readonly baseSheetHash?: string; readonly workerSheetHash?: string; readonly headSheetHash?: string;
242
244
  readonly workerChangedDeclarations?: number; readonly headChangedDeclarations?: number;
243
245
  readonly workerChangedCssModuleContracts?: number; readonly headChangedCssModuleContracts?: number;
244
- readonly parserEvidence?: CssSafeMergeParserEvidence; readonly selectorTargetEvidence?: CssSafeMergeSelectorTargetEvidence; readonly dependencyGraphEvidence?: CssDependencyGraphEvidence;
246
+ readonly parserEvidence?: CssSafeMergeParserEvidence; readonly shorthandExpansionEvidence?: CssSafeMergeShorthandExpansionEvidence; readonly selectorTargetEvidence?: CssSafeMergeSelectorTargetEvidence; readonly dependencyGraphEvidence?: CssDependencyGraphEvidence;
247
+ readonly cssModuleContractProofs?: readonly CssModuleContractProofRecord[];
245
248
  readonly cascadeRuntimeProofs?: readonly CssCascadeRuntimeProofRecord[];
246
249
  readonly dependencyGraphProofs?: readonly CssDependencyGraphProofRecord[];
247
250
  }
@@ -286,19 +289,17 @@ export interface CssSelectorTargetEquivalence {
286
289
 
287
290
  export interface CssSafeMergeInput {
288
291
  readonly id?: string; readonly sourcePath?: string; readonly baseSourceText?: string; readonly workerSourceText?: string; readonly headSourceText?: string;
289
- readonly cssModule?: boolean; readonly cssModules?: boolean;
290
- readonly generatedClassNameMap?: Readonly<Record<string, string>>;
292
+ readonly cssModule?: boolean; readonly cssModules?: boolean; readonly generatedClassNameMap?: Readonly<Record<string, string>>;
291
293
  readonly generatedClassNameMapHash?: string; readonly jsTsUseSiteGraphHash?: string; readonly cssModuleCompositionGraphHash?: string; readonly icssGraphHash?: string; readonly scopedCascadeGraphHash?: string;
292
- readonly cssCascadeRuntimeProof?: CssCascadeRuntimeProof; readonly cssCascadeRuntimeProofs?: readonly CssCascadeRuntimeProof[];
293
- readonly cssCascadeRuntimeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
294
+ readonly cssCascadeRuntimeProof?: CssCascadeRuntimeProof; readonly cssCascadeRuntimeProofs?: readonly CssCascadeRuntimeProof[]; readonly cssCascadeRuntimeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
294
295
  readonly cssSourceBoundCascadeProof?: CssCascadeRuntimeProof; readonly cssSourceBoundCascadeProofs?: readonly CssCascadeRuntimeProof[];
295
296
  readonly cssSourceBoundCascadeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
296
297
  readonly cascadeRuntimeProof?: CssCascadeRuntimeProof; readonly cascadeRuntimeProofs?: readonly CssCascadeRuntimeProof[];
297
298
  readonly cascadeRuntimeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
298
- readonly sourceBoundCascadeProof?: CssCascadeRuntimeProof; readonly sourceBoundCascadeProofs?: readonly CssCascadeRuntimeProof[];
299
- readonly sourceBoundCascadeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
299
+ readonly sourceBoundCascadeProof?: CssCascadeRuntimeProof; readonly sourceBoundCascadeProofs?: readonly CssCascadeRuntimeProof[]; readonly sourceBoundCascadeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
300
300
  readonly cssDependencyGraphProof?: CssDependencyGraphProof; readonly cssDependencyGraphProofs?: readonly CssDependencyGraphProof[]; readonly cssSourceBoundDependencyGraphProof?: CssDependencyGraphProof; readonly cssSourceBoundDependencyGraphProofs?: readonly CssDependencyGraphProof[];
301
301
  readonly cssDependencyGraphProofsByPath?: Readonly<Record<string, CssDependencyGraphProof | readonly CssDependencyGraphProof[]>>; readonly cssSourceBoundDependencyGraphProofsByPath?: Readonly<Record<string, CssDependencyGraphProof | readonly CssDependencyGraphProof[]>>;
302
+ readonly cssModuleContractProof?: CssModuleContractProof; readonly cssModuleContractProofs?: readonly CssModuleContractProof[]; readonly cssModuleContractProofsByPath?: Readonly<Record<string, CssModuleContractProof | readonly CssModuleContractProof[]>>; readonly cssSourceBoundModuleContractProof?: CssModuleContractProof; readonly cssSourceBoundModuleContractProofs?: readonly CssModuleContractProof[]; readonly cssSourceBoundModuleContractProofsByPath?: Readonly<Record<string, CssModuleContractProof | readonly CssModuleContractProof[]>>;
302
303
  readonly selectorTargetGraphHash?: string; readonly selectorTargetEquivalences?: readonly CssSelectorTargetEquivalence[];
303
304
  readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
304
305
  readonly baseGeneratedClassNameMapHash?: string; readonly workerGeneratedClassNameMapHash?: string; readonly headGeneratedClassNameMapHash?: string;
@@ -0,0 +1,146 @@
1
+ const CssModuleContractProofKinds = new Set(['css-source-bound-module-contract-proof', 'css-module-contract-proof', 'css-source-bound-css-module-contract-proof']);
2
+
3
+ function admitCssModuleContractProofs({ id, sourcePath, input = {}, moduleChanges, binding, hash }) {
4
+ const proofs = cssModuleContractProofCandidates(input, sourcePath);
5
+ const admitted = [];
6
+ const conflicts = [
7
+ ...cssModuleOverlapConflicts(id, sourcePath, moduleChanges.worker, moduleChanges.head)
8
+ ];
9
+ for (const change of [...moduleChanges.worker, ...moduleChanges.head]) {
10
+ const proof = proofs.find((candidate) => isProofForChange(candidate, change, sourcePath, binding, hash));
11
+ if (proof) admitted.push(proofRecord(proof, change, sourcePath, binding, hash));
12
+ else conflicts.push(...proofConflictsForChange(id, sourcePath, change));
13
+ }
14
+ return { proofs: admitted, conflicts };
15
+ }
16
+
17
+ function proofConflictsForChange(id, sourcePath, change) {
18
+ const contract = change.after ?? change.before;
19
+ const requiredCodes = contract?.requiredProofGapCodes ?? [];
20
+ const gapConflicts = (change.proofGaps ?? [])
21
+ .filter((gap) => requiredCodes.includes(gap.code))
22
+ .map((gap) => conflict(id, sourcePath, 'css-module-proof-gap-blocked', gap.code, {
23
+ contractKey: change.key,
24
+ contractKind: contract.contractKind,
25
+ side: change.side,
26
+ changeKind: change.kind,
27
+ proofGap: gap
28
+ }));
29
+ if (gapConflicts.length) return gapConflicts;
30
+ return [conflict(id, sourcePath, 'css-module-contract-source-proof-blocked', 'css-module-contract-source-proof-unproved', {
31
+ contractKey: change.key,
32
+ contractKind: contract?.contractKind,
33
+ side: change.side,
34
+ changeKind: change.kind,
35
+ nextProof: 'css-source-bound-module-contract-proof'
36
+ })];
37
+ }
38
+
39
+ function cssModuleOverlapConflicts(id, sourcePath, workerChanges, headChanges) {
40
+ const headByKey = new Map(headChanges.map((change) => [change.key, change]));
41
+ return workerChanges.flatMap((workerChange) => {
42
+ const headChange = headByKey.get(workerChange.key);
43
+ if (!headChange || sameContractChange(workerChange, headChange)) return [];
44
+ const contract = workerChange.after ?? workerChange.before ?? headChange.after ?? headChange.before;
45
+ return [conflict(id, sourcePath, 'css-module-contract-conflict', 'css-module-contract-conflict', {
46
+ contractKey: workerChange.key,
47
+ contractKind: contract?.contractKind,
48
+ worker: contractChangeDetails(workerChange),
49
+ head: contractChangeDetails(headChange)
50
+ })];
51
+ });
52
+ }
53
+
54
+ function isProofForChange(proof, change, sourcePath, binding, hash) {
55
+ const contract = change.after ?? change.before;
56
+ return Boolean(proof && contract && typeof proof === 'object') &&
57
+ CssModuleContractProofKinds.has(proof.kind) &&
58
+ proof.status === 'passed' &&
59
+ proof.sourcePath === sourcePath &&
60
+ proofCoversValue(proof.side, proof.sides, change.side) &&
61
+ proofCoversValue(proof.changeKind, proof.changeKinds, change.kind) &&
62
+ proofCoversValue(proof.contractKey, proof.contractKeys, change.key) &&
63
+ proofCoversValue(proof.contractKind, proof.contractKinds, contract.contractKind) &&
64
+ proofSourceMatches(proof, 'base', binding.base, hash) &&
65
+ proofSourceMatches(proof, 'worker', binding.worker, hash) &&
66
+ proofSourceMatches(proof, 'head', binding.head, hash) &&
67
+ proofSourceMatches(proof, 'output', binding.output, hash) &&
68
+ contractHashesMatch(proof, contract);
69
+ }
70
+
71
+ function contractHashesMatch(proof, contract) {
72
+ return proofHashMatches(proof, 'moduleHash', contract.moduleHash) &&
73
+ proofHashMatches(proof, 'generatedClassNameMapHash', contract.generatedClassNameMapHash, contract.contractKind === 'css-module-export') &&
74
+ proofHashMatches(proof, 'jsTsUseSiteGraphHash', contract.jsTsUseSiteGraphHash, contract.contractKind === 'css-module-export') &&
75
+ proofHashMatches(proof, 'cssModuleCompositionGraphHash', contract.cssModuleCompositionGraphHash, contract.contractKind === 'css-module-composition') &&
76
+ proofHashMatches(proof, 'icssGraphHash', contract.icssGraphHash, contract.contractKind === 'icss-import' || contract.contractKind === 'icss-export');
77
+ }
78
+
79
+ function proofHashMatches(proof, field, expected, required = true) {
80
+ if (!required) return true;
81
+ if (!expected) return false;
82
+ return proof[field] === expected || proof.contractGraphHashes?.[field] === expected || proof.cssModuleGraphHashes?.[field] === expected;
83
+ }
84
+
85
+ function proofSourceMatches(proof, role, sourceText, hash) {
86
+ if (typeof sourceText !== 'string') return false;
87
+ const sourceHash = hash?.(sourceText);
88
+ const textFields = role === 'output' ? ['outputSourceText', 'mergedSourceText'] : [`${role}SourceText`];
89
+ const hashFields = role === 'output' ? ['outputSourceHash', 'mergedSourceHash'] : [`${role}SourceHash`];
90
+ const aliases = role === 'output' ? ['output', 'merged'] : [role];
91
+ return textFields.some((field) => proof[field] === sourceText) ||
92
+ aliases.some((alias) => proof.sourceTexts?.[alias] === sourceText || proof.sources?.[alias] === sourceText) ||
93
+ hashFields.some((field) => sourceHash !== undefined && proof[field] === sourceHash) ||
94
+ aliases.some((alias) => sourceHash !== undefined && (proof.sourceHashes?.[alias] === sourceHash || proof.hashes?.[alias] === sourceHash));
95
+ }
96
+
97
+ function proofRecord(proof, change, sourcePath, binding, hash) {
98
+ const contract = change.after ?? change.before;
99
+ return {
100
+ id: proof.id,
101
+ kind: proof.kind,
102
+ status: 'passed',
103
+ proofLevel: proof.proofLevel ?? 'css-module-contract-source-bound',
104
+ sourcePath,
105
+ side: change.side,
106
+ changeKind: change.kind,
107
+ contractKey: change.key,
108
+ contractKind: contract.contractKind,
109
+ baseSourceHash: hash?.(binding.base),
110
+ workerSourceHash: hash?.(binding.worker),
111
+ headSourceHash: hash?.(binding.head),
112
+ outputSourceHash: hash?.(binding.output),
113
+ moduleHash: contract.moduleHash,
114
+ generatedClassNameMapHash: contract.generatedClassNameMapHash,
115
+ jsTsUseSiteGraphHash: contract.jsTsUseSiteGraphHash,
116
+ cssModuleCompositionGraphHash: contract.cssModuleCompositionGraphHash,
117
+ icssGraphHash: contract.icssGraphHash
118
+ };
119
+ }
120
+
121
+ function cssModuleContractProofCandidates(input = {}, sourcePath) {
122
+ return [
123
+ input.cssModuleContractProof,
124
+ input.cssModuleContractProofs,
125
+ input.cssModuleContractProofsByPath?.[sourcePath],
126
+ input.cssSourceBoundModuleContractProof,
127
+ input.cssSourceBoundModuleContractProofs,
128
+ input.cssSourceBoundModuleContractProofsByPath?.[sourcePath],
129
+ input.cssSourceBoundCssModuleContractProof,
130
+ input.cssSourceBoundCssModuleContractProofs,
131
+ input.cssSourceBoundCssModuleContractProofsByPath?.[sourcePath]
132
+ ].flatMap(asArray).filter(Boolean);
133
+ }
134
+
135
+ function proofCoversValue(single, many, value) {
136
+ if (single !== undefined) return single === value;
137
+ if (Array.isArray(many)) return many.includes(value);
138
+ return true;
139
+ }
140
+
141
+ function sameContractChange(left, right) { return (left.after?.hash ?? '') === (right.after?.hash ?? '') && left.kind === right.kind; }
142
+ function contractChangeDetails(change) { return { kind: change.kind, key: change.key, hash: change.after?.hash ?? change.before?.hash }; }
143
+ function asArray(value) { return Array.isArray(value) ? value : value === undefined ? [] : [value]; }
144
+ function conflict(id, sourcePath, code, reasonCode, details = {}) { return { code, gateId: 'css-semantic-merge', sourcePath, details: { reasonCode, conflictKey: `css#${id}#${reasonCode}#${details.contractKey ?? sourcePath ?? 'source'}`, ...details } }; }
145
+
146
+ export { admitCssModuleContractProofs };
@@ -35,6 +35,7 @@ function cssModuleContractIndex(sheet, hash) {
35
35
  for (const entry of cssModules.exports ?? []) {
36
36
  const contractHash = hash?.({ kind: 'frontier.lang.css.module.export.contract.v1', name: entry.name, generatedName: entry.generatedName });
37
37
  contracts.set(`export:${entry.name}`, {
38
+ ...contractEvidence(cssModules),
38
39
  key: `export:${entry.name}`,
39
40
  contractKind: 'css-module-export',
40
41
  name: entry.name,
@@ -45,6 +46,7 @@ function cssModuleContractIndex(sheet, hash) {
45
46
  for (const entry of cssModules.compositions ?? []) {
46
47
  const key = ['composition', entry.localName, entry.sourceKind, entry.source ?? 'local'].join(':');
47
48
  contracts.set(key, {
49
+ ...contractEvidence(cssModules),
48
50
  key,
49
51
  contractKind: 'css-module-composition',
50
52
  name: entry.localName,
@@ -55,6 +57,7 @@ function cssModuleContractIndex(sheet, hash) {
55
57
  for (const entry of cssModules.icssImports ?? []) {
56
58
  const key = ['icss-import', entry.source, entry.importedName].join(':');
57
59
  contracts.set(key, {
60
+ ...contractEvidence(cssModules),
58
61
  key,
59
62
  contractKind: 'icss-import',
60
63
  name: entry.localName,
@@ -65,6 +68,7 @@ function cssModuleContractIndex(sheet, hash) {
65
68
  for (const entry of cssModules.icssExports ?? []) {
66
69
  const key = `icss-export:${entry.name}`;
67
70
  contracts.set(key, {
71
+ ...contractEvidence(cssModules),
68
72
  key,
69
73
  contractKind: 'icss-export',
70
74
  name: entry.name,
@@ -75,6 +79,16 @@ function cssModuleContractIndex(sheet, hash) {
75
79
  return { contracts, proofGaps: cssModules.proofGaps ?? [], moduleHash: cssModules.moduleHash };
76
80
  }
77
81
 
82
+ function contractEvidence(cssModules) {
83
+ return {
84
+ moduleHash: cssModules.moduleHash,
85
+ generatedClassNameMapHash: cssModules.generatedClassNameMapHash,
86
+ jsTsUseSiteGraphHash: cssModules.jsTsUseSiteGraphHash,
87
+ cssModuleCompositionGraphHash: cssModules.cssModuleCompositionGraphHash,
88
+ icssGraphHash: cssModules.icssGraphHash
89
+ };
90
+ }
91
+
78
92
  function changedContracts(baseIndex, currentIndex, side, sheet) {
79
93
  const keys = unique([...baseIndex.contracts.keys(), ...currentIndex.contracts.keys()]);
80
94
  return keys.flatMap((key) => {
@@ -0,0 +1,38 @@
1
+ function mergeShorthandExpansionEvidence(indexes, changed) {
2
+ const sides = Object.fromEntries(Object.entries(indexes).map(([side, index]) => [side, shorthandExpansionSideEvidence(index)]));
3
+ const changedEntries = [...changed.worker, ...changed.head].flatMap((change) => [change.before, change.after].filter(Boolean));
4
+ const changedShorthands = changedEntries.filter((entry, index, entries) => entry.shorthandExpansion && entries.findIndex((candidate) => candidate.key === entry.key && candidate.declarationHash === entry.declarationHash) === index);
5
+ const expanded = changedShorthands.filter((entry) => entry.shorthandExpansion.status === 'expanded');
6
+ const unsupported = changedShorthands.filter((entry) => entry.shorthandExpansion.status !== 'expanded');
7
+ return {
8
+ kind: 'frontier.lang.cssSafeMergeShorthandExpansionEvidence',
9
+ version: 1,
10
+ shorthandDeclarationCount: Object.values(sides).reduce((sum, side) => sum + side.shorthandDeclarationCount, 0),
11
+ changedShorthandCount: changedShorthands.length,
12
+ expandedChangedShorthandCount: expanded.length,
13
+ unsupportedChangedShorthandCount: unsupported.length,
14
+ deterministicExpansionClaim: changedShorthands.length > 0 && unsupported.length === 0,
15
+ sides,
16
+ changedShorthands: changedShorthands.map((entry) => ({
17
+ cascadeKey: entry.key,
18
+ property: entry.property,
19
+ value: entry.value,
20
+ expansion: entry.shorthandExpansion
21
+ }))
22
+ };
23
+ }
24
+
25
+ function shorthandExpansionSideEvidence(index) {
26
+ const expansions = [...index.declarations.values()].filter((entry) => entry.shorthandExpansion).map((entry) => entry.shorthandExpansion);
27
+ return {
28
+ shorthandDeclarationCount: expansions.length,
29
+ expandedShorthandDeclarations: expansions.filter((entry) => entry.status === 'expanded').length,
30
+ unsupportedShorthandDeclarations: expansions.filter((entry) => entry.status !== 'expanded').length,
31
+ supportedProperties: unique(expansions.filter((entry) => entry.status === 'expanded').map((entry) => entry.property)),
32
+ unsupportedProperties: unique(expansions.filter((entry) => entry.status !== 'expanded').map((entry) => entry.property))
33
+ };
34
+ }
35
+
36
+ function unique(values) { return [...new Set(values.filter(Boolean))]; }
37
+
38
+ export { mergeShorthandExpansionEvidence };
@@ -6,6 +6,38 @@ function shorthandGroupForProperty(property) {
6
6
  return undefined;
7
7
  }
8
8
 
9
+ function deterministicShorthandExpansion(property, value, hash) {
10
+ const normalized = normalizeProperty(property);
11
+ if (!normalized || !ShorthandExpansionKeys.has(normalized)) return undefined;
12
+ const definition = DeterministicShorthandExpansions.get(normalized);
13
+ const unsupported = (reasonCode) => ({
14
+ kind: 'frontier.lang.cssShorthandExpansionEvidence',
15
+ version: 1,
16
+ property: normalized,
17
+ value: String(value ?? ''),
18
+ status: 'unsupported',
19
+ deterministic: false,
20
+ reasonCode,
21
+ expansionHash: hash?.({ kind: 'frontier.lang.css.shorthandExpansion.v1', property: normalized, value: String(value ?? ''), status: 'unsupported', reasonCode })
22
+ });
23
+ if (!definition) return unsupported('css-shorthand-expansion-unsupported');
24
+ const tokens = cssValueTokens(value);
25
+ if (!tokens.length || tokens.length > definition.maxTokens) return unsupported('css-shorthand-expansion-unsupported-value-shape');
26
+ if (tokens.some(isRuntimeSubstitutionToken)) return unsupported('css-shorthand-expansion-runtime-substitution');
27
+ const longhands = expandTokens(definition.properties, tokens);
28
+ return {
29
+ kind: 'frontier.lang.cssShorthandExpansionEvidence',
30
+ version: 1,
31
+ property: normalized,
32
+ value: String(value ?? ''),
33
+ group: normalized,
34
+ status: 'expanded',
35
+ deterministic: true,
36
+ longhands,
37
+ expansionHash: hash?.({ kind: 'frontier.lang.css.shorthandExpansion.v1', property: normalized, value: String(value ?? ''), longhands })
38
+ };
39
+ }
40
+
9
41
  function declarationsOverlapByCssProperty(leftProperty, rightProperty) {
10
42
  const left = normalizeProperty(leftProperty);
11
43
  const right = normalizeProperty(rightProperty);
@@ -28,6 +60,80 @@ function normalizeProperty(property) {
28
60
  return String(property ?? '').trim().toLowerCase();
29
61
  }
30
62
 
63
+ function cssValueTokens(value) {
64
+ const text = String(value ?? '').trim();
65
+ if (!text) return [];
66
+ const tokens = [];
67
+ let token = '';
68
+ let depth = 0;
69
+ let quote = '';
70
+ let escaped = false;
71
+ for (const char of text) {
72
+ if (escaped) {
73
+ token += char;
74
+ escaped = false;
75
+ continue;
76
+ }
77
+ if (char === '\\') {
78
+ token += char;
79
+ escaped = true;
80
+ continue;
81
+ }
82
+ if (quote) {
83
+ token += char;
84
+ if (char === quote) quote = '';
85
+ continue;
86
+ }
87
+ if (char === '"' || char === "'") {
88
+ token += char;
89
+ quote = char;
90
+ continue;
91
+ }
92
+ if (char === '(') {
93
+ token += char;
94
+ depth += 1;
95
+ continue;
96
+ }
97
+ if (char === ')') {
98
+ token += char;
99
+ depth = Math.max(0, depth - 1);
100
+ continue;
101
+ }
102
+ if (/\s/.test(char) && depth === 0) {
103
+ if (token) tokens.push(token);
104
+ token = '';
105
+ continue;
106
+ }
107
+ token += char;
108
+ }
109
+ if (token) tokens.push(token);
110
+ return tokens;
111
+ }
112
+
113
+ function isRuntimeSubstitutionToken(token) {
114
+ return /\b(?:var|env)\s*\(/i.test(String(token ?? ''));
115
+ }
116
+
117
+ function expandTokens(properties, tokens) {
118
+ if (properties.length === 4) {
119
+ const [top, right = top, bottom = top, left = right] = tokens;
120
+ return [
121
+ { property: properties[0], value: top },
122
+ { property: properties[1], value: right },
123
+ { property: properties[2], value: bottom },
124
+ { property: properties[3], value: left }
125
+ ];
126
+ }
127
+ if (properties.length === 2) {
128
+ const [first, second = first] = tokens;
129
+ return [
130
+ { property: properties[0], value: first },
131
+ { property: properties[1], value: second }
132
+ ];
133
+ }
134
+ return properties.map((longhand, index) => ({ property: longhand, value: tokens[index] ?? tokens[0] }));
135
+ }
136
+
31
137
  function allPropertyExcluded(property) {
32
138
  return property.startsWith('--') || property === 'direction' || property === 'unicode-bidi';
33
139
  }
@@ -132,6 +238,32 @@ const ShorthandExpansionKeys = new Map(Object.entries({
132
238
  transition: ['transition-behavior', 'transition-delay', 'transition-duration', 'transition-property', 'transition-timing-function']
133
239
  }).map(([property, keys]) => [property, new Set(keys)]));
134
240
 
241
+ const DeterministicShorthandExpansions = new Map(Object.entries({
242
+ margin: box4('margin', ['top', 'right', 'bottom', 'left']),
243
+ padding: box4('padding', ['top', 'right', 'bottom', 'left']),
244
+ inset: { properties: ['top', 'right', 'bottom', 'left'], maxTokens: 4 },
245
+ 'margin-block': { properties: ['margin-block-start', 'margin-block-end'], maxTokens: 2 },
246
+ 'margin-inline': { properties: ['margin-inline-start', 'margin-inline-end'], maxTokens: 2 },
247
+ 'padding-block': { properties: ['padding-block-start', 'padding-block-end'], maxTokens: 2 },
248
+ 'padding-inline': { properties: ['padding-inline-start', 'padding-inline-end'], maxTokens: 2 },
249
+ 'inset-block': { properties: ['inset-block-start', 'inset-block-end'], maxTokens: 2 },
250
+ 'inset-inline': { properties: ['inset-inline-start', 'inset-inline-end'], maxTokens: 2 },
251
+ gap: { properties: ['row-gap', 'column-gap'], maxTokens: 2 },
252
+ 'scroll-margin': box4('scroll-margin', ['top', 'right', 'bottom', 'left']),
253
+ 'scroll-padding': box4('scroll-padding', ['top', 'right', 'bottom', 'left']),
254
+ 'scroll-margin-block': { properties: ['scroll-margin-block-start', 'scroll-margin-block-end'], maxTokens: 2 },
255
+ 'scroll-margin-inline': { properties: ['scroll-margin-inline-start', 'scroll-margin-inline-end'], maxTokens: 2 },
256
+ 'scroll-padding-block': { properties: ['scroll-padding-block-start', 'scroll-padding-block-end'], maxTokens: 2 },
257
+ 'scroll-padding-inline': { properties: ['scroll-padding-inline-start', 'scroll-padding-inline-end'], maxTokens: 2 },
258
+ 'border-width': box4('border', ['top-width', 'right-width', 'bottom-width', 'left-width']),
259
+ 'border-style': box4('border', ['top-style', 'right-style', 'bottom-style', 'left-style']),
260
+ 'border-color': box4('border', ['top-color', 'right-color', 'bottom-color', 'left-color'])
261
+ }));
262
+
263
+ function box4(prefix, suffixes) {
264
+ return { properties: suffixes.map((suffix) => `${prefix}-${suffix}`), maxTokens: 4 };
265
+ }
266
+
135
267
  function edgeKeys(prefix, attributes) {
136
268
  return ['top', 'right', 'bottom', 'left'].flatMap((edge) => attributes.map((attribute) => `${prefix}-${edge}-${attribute}`));
137
269
  }
@@ -144,4 +276,4 @@ function propertyTuple(prefix, attributes) {
144
276
  return attributes.map((attribute) => `${prefix}-${attribute}`);
145
277
  }
146
278
 
147
- export { declarationsOverlapByCssProperty, shorthandGroupForProperty };
279
+ export { declarationsOverlapByCssProperty, deterministicShorthandExpansion, shorthandGroupForProperty };
@@ -1,9 +1,11 @@
1
- import { cssModuleContractChanges, cssModuleContractConflicts, sheetOptions, unsupportedSourceShapeChanges } from './semantic-merge-css-modules.js';
1
+ import { cssModuleContractChanges, sheetOptions, unsupportedSourceShapeChanges } from './semantic-merge-css-modules.js';
2
+ import { admitCssModuleContractProofs } from './semantic-merge-css-module-proofs.js';
2
3
  import { admitCascadeRuntimeProofs } from './semantic-merge-cascade-runtime.js';
3
4
  import { admitCssDependencyGraphProofs, mergeCssDependencyGraphEvidence } from './dependency-graph.js';
4
5
  import { mergeSelectorTargetEvidence, planSelectorTargetRebase } from './semantic-merge-selector-targets.js';
5
6
  import { applyAtRuleBlockChanges, atRuleBlockEntry, atRuleBlockOverlapConflicts, atRuleOccurrenceKey, changedAtRuleBlocks, renderAtRuleBlock, renderAtRuleStatement } from './semantic-merge-at-rules.js';
6
- import { declarationsOverlapByCssProperty, shorthandGroupForProperty } from './semantic-merge-shorthand.js';
7
+ import { declarationsOverlapByCssProperty, deterministicShorthandExpansion, shorthandGroupForProperty } from './semantic-merge-shorthand.js';
8
+ import { mergeShorthandExpansionEvidence } from './semantic-merge-shorthand-evidence.js';
7
9
  import { duplicateCascadeKeyConflictsForIndexes } from './semantic-merge-duplicate-cascade.js';
8
10
 
9
11
  function safeMergeCssSource(input = {}, context = {}) {
@@ -21,7 +23,7 @@ function safeMergeCssSource(input = {}, context = {}) {
21
23
  worker: parseSheet(worker, sheetOptions(input, 'worker', sourcePath)),
22
24
  head: parseSheet(head, sheetOptions(input, 'head', sourcePath))
23
25
  };
24
- const indexes = Object.fromEntries(Object.entries(sheets).map(([name, sheet]) => [name, declarationIndex(sheet)]));
26
+ const indexes = Object.fromEntries(Object.entries(sheets).map(([name, sheet]) => [name, declarationIndex(sheet, hash)]));
25
27
  const changed = {
26
28
  worker: changedDeclarations(indexes.base, indexes.worker, 'worker'),
27
29
  head: changedDeclarations(indexes.base, indexes.head, 'head')
@@ -39,8 +41,8 @@ function safeMergeCssSource(input = {}, context = {}) {
39
41
  ...shorthandOverlapConflicts(id, sourcePath, changed.worker, changed.head),
40
42
  ...atRuleBlockOverlapConflicts(id, sourcePath, blockChanges.worker, blockChanges.head, conflict)
41
43
  ];
42
- const moduleConflicts = cssModuleContractConflicts(id, sourcePath, moduleChanges);
43
44
  const parserEvidence = mergeParserEvidence(sheets);
45
+ const shorthandExpansionEvidence = mergeShorthandExpansionEvidence(indexes, changed);
44
46
  const dependencyGraphEvidence = mergeCssDependencyGraphEvidence(sheets, changed);
45
47
  const selectorTargetPlan = planSelectorTargetRebase(id, sourcePath, mergeSelectorTargetEvidence(sheets, changed), changed, input);
46
48
  const shapeChanges = unsupportedSourceShapeChanges(sheets, changed, hash);
@@ -55,8 +57,9 @@ function safeMergeCssSource(input = {}, context = {}) {
55
57
  hash
56
58
  });
57
59
  const dependencyGraphAdmission = admitCssDependencyGraphProofs({ id, sourcePath, input, dependencyGraphEvidence, binding: { base, worker, head, output: mergedSourceText }, hash });
58
- const conflicts = [...parserConflicts, ...duplicateCascadeKeyConflicts, ...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...cascadeRuntimeAdmission.conflicts, ...dependencyGraphAdmission.conflicts, ...selectorTargetPlan.conflicts];
59
- if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, dependencyGraphEvidence, selectorTargetEvidence: selectorTargetPlan.evidence, cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs, dependencyGraphProofs: dependencyGraphAdmission.proofs });
60
+ const cssModuleAdmission = admitCssModuleContractProofs({ id, sourcePath, input, moduleChanges, binding: { base, worker, head, output: mergedSourceText }, hash });
61
+ const conflicts = [...parserConflicts, ...duplicateCascadeKeyConflicts, ...proofConflicts, ...overlapConflicts, ...cssModuleAdmission.conflicts, ...cascadeRuntimeAdmission.conflicts, ...dependencyGraphAdmission.conflicts, ...selectorTargetPlan.conflicts];
62
+ if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, shorthandExpansionEvidence, dependencyGraphEvidence, selectorTargetEvidence: selectorTargetPlan.evidence, cssModuleContractProofs: cssModuleAdmission.proofs, cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs, dependencyGraphProofs: dependencyGraphAdmission.proofs });
60
63
  return merged(id, sourcePath, mergedSourceText, 'semantic-declaration-merge', hash, {
61
64
  baseSheetHash: sheets.base.sheetHash,
62
65
  workerSheetHash: sheets.worker.sheetHash,
@@ -66,15 +69,17 @@ function safeMergeCssSource(input = {}, context = {}) {
66
69
  workerChangedCssModuleContracts: moduleChanges.worker.length,
67
70
  headChangedCssModuleContracts: moduleChanges.head.length,
68
71
  parserEvidence,
72
+ shorthandExpansionEvidence,
69
73
  dependencyGraphEvidence,
70
74
  selectorTargetEvidence: selectorTargetPlan.evidence,
75
+ cssModuleContractProofs: cssModuleAdmission.proofs,
71
76
  cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs,
72
77
  dependencyGraphProofs: dependencyGraphAdmission.proofs,
73
78
  browserCascadeEquivalenceClaim: cascadeRuntimeAdmission.proofs.length > 0
74
79
  });
75
80
  }
76
81
 
77
- function declarationIndex(sheet) {
82
+ function declarationIndex(sheet, hash) {
78
83
  const declarations = new Map();
79
84
  const order = [];
80
85
  const statements = [];
@@ -102,6 +107,7 @@ function declarationIndex(sheet) {
102
107
  important: declaration.important,
103
108
  declarationOrdinal: declaration.ordinal,
104
109
  declarationHash: declaration.declarationHash,
110
+ shorthandExpansion: deterministicShorthandExpansion(declaration.property, declaration.value, hash),
105
111
  selectorTargetGraphHash: record.selectorTargetGraphHash,
106
112
  proofGaps: proofGapsForDeclaration(record, declaration)
107
113
  };
@@ -134,6 +140,8 @@ function proofGapConflicts(id, sourcePath, changed, indexes) {
134
140
  .filter((gap) => !canAdmitProofGap(gap, entry, changed, indexes))
135
141
  .map((gap) => conflict(id, sourcePath, 'css-proof-gap-blocked', gap.code, {
136
142
  cascadeKey: key,
143
+ property: entry.property,
144
+ shorthandExpansion: entry.shorthandExpansion,
137
145
  proofGap: gap
138
146
  }));
139
147
  });
@@ -202,7 +210,7 @@ function shorthandOverlapConflicts(id, sourcePath, workerChanges, headChanges) {
202
210
  function canAdmitProofGap(gap, entry, changed, indexes) {
203
211
  if (gap.code !== 'css-shorthand-expansion-unproved' || !entry) return false;
204
212
  const group = shorthandGroupForProperty(entry.property);
205
- if (!group || hasRelatedExistingDeclaration(entry, indexes)) return false;
213
+ if (!group || entry.shorthandExpansion?.status !== 'expanded' || hasRelatedExistingDeclaration(entry, indexes)) return false;
206
214
  const oppositeChanges = [...changed.worker, ...changed.head].filter((change) => change.key !== entry.key);
207
215
  return !oppositeChanges.some((change) => declarationsOverlapByShorthandGroup(entry, change.after ?? change.before));
208
216
  }
@@ -239,9 +247,7 @@ function renderDeclarationIndex(index) {
239
247
  const chunks = [];
240
248
  for (const statement of index.statements ?? []) renderAtRuleStatement(chunks, statement);
241
249
  for (const key of index.atRuleBlockOrder ?? []) renderAtRuleBlock(chunks, index.atRuleBlocks.get(key));
242
- for (const declarations of groups.values()) {
243
- renderDeclarationGroup(chunks, declarations);
244
- }
250
+ for (const declarations of groups.values()) renderDeclarationGroup(chunks, declarations);
245
251
  return `${chunks.join('\n').trimEnd()}\n`;
246
252
  }
247
253
 
@@ -263,21 +269,11 @@ function renderDeclarationGroup(chunks, declarations) {
263
269
  }
264
270
 
265
271
  function merged(id, sourcePath, sourceText, operation, hash, extra = {}) {
266
- return result(id, sourcePath, 'merged', {
267
- operation,
268
- mergedSourceText: sourceText,
269
- mergedSourceHash: hash?.(sourceText),
270
- conflicts: [],
271
- ...extra
272
- });
272
+ return result(id, sourcePath, 'merged', { operation, mergedSourceText: sourceText, mergedSourceHash: hash?.(sourceText), conflicts: [], ...extra });
273
273
  }
274
274
 
275
275
  function blocked(id, sourcePath, reasonCode, conflicts = [], extra = {}) {
276
- return result(id, sourcePath, 'blocked', {
277
- operation: 'blocked',
278
- conflicts: conflicts.length ? conflicts : [conflict(id, sourcePath, reasonCode, reasonCode)],
279
- ...extra
280
- });
276
+ return result(id, sourcePath, 'blocked', { operation: 'blocked', conflicts: conflicts.length ? conflicts : [conflict(id, sourcePath, reasonCode, reasonCode)], ...extra });
281
277
  }
282
278
 
283
279
  function result(id, sourcePath, status, body) {
@@ -299,7 +295,7 @@ function result(id, sourcePath, status, body) {
299
295
  reviewRequired: status !== 'merged',
300
296
  reasonCodes: unique((body.conflicts ?? []).map((item) => item.details.reasonCode)),
301
297
  browserCascadeEquivalenceClaim: browserCascadeEquivalenceClaim || undefined,
302
- cssCascadeRuntimeProofs: body.cascadeRuntimeProofs?.length ? body.cascadeRuntimeProofs : undefined, cssDependencyGraphProofs: body.dependencyGraphProofs?.length ? body.dependencyGraphProofs : undefined
298
+ cssCascadeRuntimeProofs: body.cascadeRuntimeProofs?.length ? body.cascadeRuntimeProofs : undefined, cssDependencyGraphProofs: body.dependencyGraphProofs?.length ? body.dependencyGraphProofs : undefined, cssModuleContractProofs: body.cssModuleContractProofs?.length ? body.cssModuleContractProofs : undefined
303
299
  }
304
300
  };
305
301
  }
@@ -0,0 +1,44 @@
1
+ export interface CssShorthandLonghandExpansion {
2
+ readonly property: string;
3
+ readonly value: string;
4
+ }
5
+
6
+ export interface CssShorthandExpansionEvidence {
7
+ readonly kind: 'frontier.lang.cssShorthandExpansionEvidence';
8
+ readonly version: 1;
9
+ readonly property: string;
10
+ readonly value: string;
11
+ readonly group?: string;
12
+ readonly status: 'expanded' | 'unsupported' | string;
13
+ readonly deterministic: boolean;
14
+ readonly reasonCode?: string;
15
+ readonly longhands?: readonly CssShorthandLonghandExpansion[];
16
+ readonly expansionHash?: string;
17
+ }
18
+
19
+ export interface CssSafeMergeShorthandExpansionEvidence {
20
+ readonly kind: 'frontier.lang.cssSafeMergeShorthandExpansionEvidence';
21
+ readonly version: 1;
22
+ readonly shorthandDeclarationCount: number;
23
+ readonly changedShorthandCount: number;
24
+ readonly expandedChangedShorthandCount: number;
25
+ readonly unsupportedChangedShorthandCount: number;
26
+ readonly deterministicExpansionClaim: boolean;
27
+ readonly sides: Readonly<Record<string, CssSafeMergeShorthandExpansionSideEvidence>>;
28
+ readonly changedShorthands: readonly CssSafeMergeChangedShorthandExpansion[];
29
+ }
30
+
31
+ export interface CssSafeMergeShorthandExpansionSideEvidence {
32
+ readonly shorthandDeclarationCount: number;
33
+ readonly expandedShorthandDeclarations: number;
34
+ readonly unsupportedShorthandDeclarations: number;
35
+ readonly supportedProperties: readonly string[];
36
+ readonly unsupportedProperties: readonly string[];
37
+ }
38
+
39
+ export interface CssSafeMergeChangedShorthandExpansion {
40
+ readonly cascadeKey: string;
41
+ readonly property: string;
42
+ readonly value: string;
43
+ readonly expansion: CssShorthandExpansionEvidence;
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-css",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
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",