@shapeshift-labs/frontier-lang-css 0.1.13 → 0.1.15

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 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 source-bound dependency graph evidence into the merge result, 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 and non-overlapping shorthand expansion sets; 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; 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: incomplete generated class-name maps, 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.
242
- - Claims: dependency graph evidence is an admission/review signal only; `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.
240
+ - Safe merge: independent declarations with non-overlapping cascade keys, 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: 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.
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.
@@ -30,6 +30,15 @@ export interface CssDependencyGraphRecordSets {
30
30
  readonly urlAssetReferences?: readonly CssDependencyGraphRecord[];
31
31
  }
32
32
 
33
+ export interface CssDependencyGraphChange {
34
+ readonly side: 'worker' | 'head' | string;
35
+ readonly cascadeKey: string;
36
+ readonly reasonCode: 'css-dependency-graph-proof-unproved' | string;
37
+ readonly changeKind?: string;
38
+ readonly before?: Readonly<Record<string, unknown>>;
39
+ readonly after?: Readonly<Record<string, unknown>>;
40
+ }
41
+
33
42
  export interface CssDependencyGraphEvidence {
34
43
  readonly kind: 'frontier.lang.cssDependencyGraphEvidence' | 'frontier.lang.cssSafeMergeDependencyGraphEvidence' | string;
35
44
  readonly version: 1;
@@ -42,6 +51,7 @@ export interface CssDependencyGraphEvidence {
42
51
  readonly dependencyGraphHash?: string;
43
52
  readonly cssDependencyGraphHash?: string;
44
53
  readonly changedDependencySurfaceCount?: number;
54
+ readonly changedDependencySurfaces?: readonly CssDependencyGraphChange[];
45
55
  readonly customPropertyDefinitions?: number;
46
56
  readonly customPropertyReferences?: number;
47
57
  readonly varReferences?: number;
@@ -58,3 +68,36 @@ export interface CssDependencyGraphEvidence {
58
68
  readonly browserRenderEquivalenceClaim?: false;
59
69
  readonly semanticEquivalenceClaim: false;
60
70
  }
71
+
72
+ export interface CssDependencyGraphProof {
73
+ readonly id?: string;
74
+ readonly kind: 'css-dependency-graph-proof' | 'css-source-bound-dependency-graph-proof' | string;
75
+ readonly status: 'passed' | string;
76
+ readonly proofLevel?: string;
77
+ readonly sourcePath?: string;
78
+ readonly reasonCode?: string; readonly reasonCodes?: readonly string[];
79
+ readonly side?: 'worker' | 'head' | string; readonly sides?: readonly string[];
80
+ readonly cascadeKey?: string; readonly cascadeKeys?: readonly string[];
81
+ readonly dependencyKey?: string; readonly dependencyKeys?: readonly string[];
82
+ readonly baseSourceText?: string; readonly workerSourceText?: string; readonly headSourceText?: string; readonly outputSourceText?: string; readonly mergedSourceText?: string;
83
+ readonly baseSourceHash?: string; readonly workerSourceHash?: string; readonly headSourceHash?: string; readonly outputSourceHash?: string; readonly mergedSourceHash?: string;
84
+ readonly sourceTexts?: Readonly<Record<string, string>>; readonly sources?: Readonly<Record<string, string>>;
85
+ readonly sourceHashes?: Readonly<Record<string, string>>; readonly hashes?: Readonly<Record<string, string>>;
86
+ readonly baseDependencyGraphHash?: string; readonly workerDependencyGraphHash?: string; readonly headDependencyGraphHash?: string;
87
+ readonly baseCssDependencyGraphHash?: string; readonly workerCssDependencyGraphHash?: string; readonly headCssDependencyGraphHash?: string;
88
+ readonly dependencyGraphHashes?: Readonly<Record<string, string>>;
89
+ readonly cssDependencyGraphHashes?: Readonly<Record<string, string>>;
90
+ }
91
+
92
+ export interface CssDependencyGraphProofRecord {
93
+ readonly id?: string;
94
+ readonly kind: string;
95
+ readonly status: 'passed';
96
+ readonly proofLevel: string;
97
+ readonly reasonCode: string;
98
+ readonly side: string;
99
+ readonly cascadeKey: string;
100
+ readonly sourcePath?: string;
101
+ readonly baseSourceHash?: string; readonly workerSourceHash?: string; readonly headSourceHash?: string; readonly outputSourceHash?: string;
102
+ readonly baseDependencyGraphHash?: string; readonly workerDependencyGraphHash?: string; readonly headDependencyGraphHash?: string;
103
+ }
@@ -72,8 +72,7 @@ function mergeCssDependencyGraphEvidence(sheets, changed = {}) {
72
72
  const sides = Object.fromEntries(Object.entries(sheets).map(([side, sheet]) => [side, sheet.dependencyGraphEvidence ?? emptyGraphEvidence(sheet)]));
73
73
  const sideValues = Object.values(sides);
74
74
  const hasDependencySurface = sideValues.some((side) => side.hasDependencySurface === true);
75
- const changedKeys = new Set([...(changed.worker ?? []), ...(changed.head ?? [])].flatMap((change) => [change.before?.key, change.after?.key].filter(Boolean)));
76
- const dependencyKeys = new Set(sideValues.flatMap(dependencySurfaceKeys));
75
+ const changedDependencySurfaces = changedDependencyGraphSurfaces(sides, changed);
77
76
  return {
78
77
  kind: 'frontier.lang.cssSafeMergeDependencyGraphEvidence',
79
78
  version: 1,
@@ -81,7 +80,8 @@ function mergeCssDependencyGraphEvidence(sheets, changed = {}) {
81
80
  dependencySurfaceCount: Math.max(0, ...sideValues.map((side) => side.dependencySurfaceCount ?? 0)),
82
81
  dependencyGraphHashPresent: hasDependencySurface && sideValues.every((side) => side.hasDependencySurface !== true || side.dependencyGraphHashPresent === true),
83
82
  cssDependencyGraphHashPresent: hasDependencySurface && sideValues.every((side) => side.hasDependencySurface !== true || side.cssDependencyGraphHashPresent === true),
84
- changedDependencySurfaceCount: [...changedKeys].filter((key) => dependencyKeys.has(key)).length,
83
+ changedDependencySurfaceCount: changedDependencySurfaces.length,
84
+ changedDependencySurfaces,
85
85
  sides,
86
86
  browserCascadeEquivalenceClaim: false,
87
87
  browserRenderEquivalenceClaim: false,
@@ -89,7 +89,37 @@ function mergeCssDependencyGraphEvidence(sheets, changed = {}) {
89
89
  };
90
90
  }
91
91
 
92
- function dependencySurfaceKeys(evidence) {
92
+ function admitCssDependencyGraphProofs({ id, sourcePath, input, dependencyGraphEvidence, binding, hash }) {
93
+ const proofs = dependencyGraphProofCandidates(input, sourcePath);
94
+ const admitted = [];
95
+ const conflicts = [];
96
+ for (const change of dependencyGraphEvidence.changedDependencySurfaces ?? []) {
97
+ const proof = proofs.find((candidate) => isDependencyGraphProofForChange(candidate, change, sourcePath, dependencyGraphEvidence, binding, hash));
98
+ if (proof) admitted.push(dependencyGraphProofRecord(proof, change, sourcePath, dependencyGraphEvidence, binding, hash));
99
+ else conflicts.push(conflict(id, sourcePath, 'css-dependency-graph-proof-blocked', change.reasonCode, change));
100
+ }
101
+ return { proofs: admitted, conflicts };
102
+ }
103
+
104
+ function changedDependencyGraphSurfaces(sides, changed) {
105
+ return Object.entries(changed).flatMap(([side, changes]) => (changes ?? []).flatMap((change) => {
106
+ const cascadeKey = change.after?.key ?? change.before?.key;
107
+ if (!cascadeKey) return [];
108
+ const before = dependencyRecordsForKey(sides.base, cascadeKey);
109
+ const after = dependencyRecordsForKey(sides[side], cascadeKey);
110
+ if (!before.length && !after.length) return [];
111
+ return [{
112
+ side,
113
+ cascadeKey,
114
+ reasonCode: 'css-dependency-graph-proof-unproved',
115
+ changeKind: change.kind,
116
+ before: dependencyRecordSummary(before),
117
+ after: dependencyRecordSummary(after)
118
+ }];
119
+ }));
120
+ }
121
+
122
+ function dependencySurfaceRecords(evidence) {
93
123
  const records = evidence.records ?? {};
94
124
  return [
95
125
  ...(records.customPropertyDefinitions ?? []),
@@ -97,7 +127,99 @@ function dependencySurfaceKeys(evidence) {
97
127
  ...(records.animationNameLinks ?? []),
98
128
  ...(records.fontFaceLinks ?? []),
99
129
  ...(records.urlAssetReferences ?? [])
100
- ].map((entry) => entry.cascadeKey).filter(Boolean);
130
+ ];
131
+ }
132
+
133
+ function dependencyRecordsForKey(evidence, cascadeKey) {
134
+ return dependencySurfaceRecords(evidence).filter((entry) => entry.cascadeKey === cascadeKey);
135
+ }
136
+
137
+ function dependencyRecordSummary(records) {
138
+ return {
139
+ dependencyKinds: unique(records.map((entry) => entry.kind)),
140
+ names: unique(records.map((entry) => entry.name ?? entry.family ?? entry.url)),
141
+ properties: unique(records.map((entry) => entry.property)),
142
+ declarationHashes: unique(records.map((entry) => entry.declarationHash))
143
+ };
144
+ }
145
+
146
+ function dependencyGraphProofCandidates(input = {}, sourcePath) {
147
+ return [
148
+ input.cssDependencyGraphProof,
149
+ input.cssDependencyGraphProofs,
150
+ input.cssDependencyGraphProofsByPath?.[sourcePath],
151
+ input.cssSourceBoundDependencyGraphProof,
152
+ input.cssSourceBoundDependencyGraphProofs,
153
+ input.cssSourceBoundDependencyGraphProofsByPath?.[sourcePath],
154
+ input.dependencyGraphProof,
155
+ input.dependencyGraphProofs,
156
+ input.dependencyGraphProofsByPath?.[sourcePath],
157
+ input.sourceBoundDependencyGraphProof,
158
+ input.sourceBoundDependencyGraphProofs,
159
+ input.sourceBoundDependencyGraphProofsByPath?.[sourcePath]
160
+ ].flatMap(asArray).filter(Boolean);
161
+ }
162
+
163
+ function isDependencyGraphProofForChange(proof, change, sourcePath, evidence, binding, hash) {
164
+ return Boolean(proof && typeof proof === 'object') &&
165
+ DependencyGraphProofKinds.has(proof.kind) &&
166
+ proof.status === 'passed' &&
167
+ proof.sourcePath === sourcePath &&
168
+ proofCoversValue(proof.reasonCode, proof.reasonCodes, change.reasonCode) &&
169
+ proofCoversValue(proof.side, proof.sides, change.side) &&
170
+ proofCoversValue(proof.cascadeKey ?? proof.dependencyKey, proof.cascadeKeys ?? proof.dependencyKeys, change.cascadeKey) &&
171
+ proofSourceMatches(proof, 'base', binding.base, hash) &&
172
+ proofSourceMatches(proof, 'worker', binding.worker, hash) &&
173
+ proofSourceMatches(proof, 'head', binding.head, hash) &&
174
+ proofSourceMatches(proof, 'output', binding.output, hash) &&
175
+ dependencyGraphHashMatches(proof, 'base', evidence.sides?.base) &&
176
+ dependencyGraphHashMatches(proof, 'worker', evidence.sides?.worker) &&
177
+ dependencyGraphHashMatches(proof, 'head', evidence.sides?.head);
178
+ }
179
+
180
+ function dependencyGraphHashMatches(proof, role, sideEvidence) {
181
+ const graphHash = sideEvidence?.dependencyGraphHash;
182
+ if (!graphHash) return true;
183
+ return proof[`${role}DependencyGraphHash`] === graphHash ||
184
+ proof[`${role}CssDependencyGraphHash`] === graphHash ||
185
+ proof.dependencyGraphHashes?.[role] === graphHash ||
186
+ proof.cssDependencyGraphHashes?.[role] === graphHash;
187
+ }
188
+
189
+ function proofSourceMatches(proof, role, sourceText, hash) {
190
+ if (typeof sourceText !== 'string') return false;
191
+ const sourceHash = hash?.(sourceText);
192
+ const textFields = role === 'output' ? ['outputSourceText', 'mergedSourceText'] : [`${role}SourceText`];
193
+ const hashFields = role === 'output' ? ['outputSourceHash', 'mergedSourceHash'] : [`${role}SourceHash`];
194
+ const aliases = role === 'output' ? ['output', 'merged'] : [role];
195
+ return textFields.some((field) => proof[field] === sourceText) ||
196
+ aliases.some((alias) => proof.sourceTexts?.[alias] === sourceText || proof.sources?.[alias] === sourceText) ||
197
+ hashFields.some((field) => sourceHash !== undefined && proof[field] === sourceHash) ||
198
+ aliases.some((alias) => sourceHash !== undefined && (proof.sourceHashes?.[alias] === sourceHash || proof.hashes?.[alias] === sourceHash));
199
+ }
200
+
201
+ function dependencyGraphProofRecord(proof, change, sourcePath, evidence, binding, hash) {
202
+ return {
203
+ id: proof.id,
204
+ kind: proof.kind,
205
+ status: 'passed',
206
+ proofLevel: proof.proofLevel ?? 'css-dependency-graph-source-bound',
207
+ reasonCode: change.reasonCode,
208
+ side: change.side,
209
+ cascadeKey: change.cascadeKey,
210
+ sourcePath,
211
+ baseSourceHash: hash?.(binding.base),
212
+ workerSourceHash: hash?.(binding.worker),
213
+ headSourceHash: hash?.(binding.head),
214
+ outputSourceHash: hash?.(binding.output),
215
+ baseDependencyGraphHash: evidence.sides?.base?.dependencyGraphHash,
216
+ workerDependencyGraphHash: evidence.sides?.worker?.dependencyGraphHash,
217
+ headDependencyGraphHash: evidence.sides?.head?.dependencyGraphHash
218
+ };
219
+ }
220
+
221
+ function conflict(id, sourcePath, code, reasonCode, details = {}) {
222
+ return { code, gateId: 'css-semantic-merge', sourcePath, details: { reasonCode, conflictKey: `css#${id}#${reasonCode}#${details.cascadeKey ?? sourcePath ?? 'source'}`, ...details } };
101
223
  }
102
224
 
103
225
  function customPropertyDefinition(record, declaration) {
@@ -184,8 +306,11 @@ function stableTextHash(text) {
184
306
 
185
307
  function unique(values) { return [...new Set(values.filter(Boolean))]; }
186
308
  function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
309
+ function proofCoversValue(value, values, expected) { return value === expected || (Array.isArray(values) && values.includes(expected)); }
310
+ function asArray(value) { return Array.isArray(value) ? value : value === undefined ? [] : [value]; }
187
311
 
188
312
  const AnimationKeywords = new Set(['none', 'initial', 'inherit', 'unset', 'revert', 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'infinite', 'alternate', 'forwards', 'backwards', 'both', 'normal', 'reverse', 'running', 'paused']);
189
313
  const FontKeywords = new Set(['serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui', 'inherit', 'initial', 'unset', 'revert']);
314
+ const DependencyGraphProofKinds = new Set(['css-dependency-graph-proof', 'css-source-bound-dependency-graph-proof']);
190
315
 
191
- export { createCssDependencyGraphEvidence, mergeCssDependencyGraphEvidence };
316
+ export { admitCssDependencyGraphProofs, createCssDependencyGraphEvidence, mergeCssDependencyGraphEvidence };
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 } from './dependency-graph.js'; export type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.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'; export type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.js'; export type { CssDependencyGraphChange, CssDependencyGraphEvidence, CssDependencyGraphProof, CssDependencyGraphProofRecord } from './dependency-graph.js';
2
2
 
3
3
  export interface CssProjectionOptions {
4
4
  readonly banner?: string;
@@ -227,6 +227,7 @@ export interface CssSafeMergeAdmission {
227
227
  readonly reviewRequired: boolean; readonly reasonCodes: readonly string[];
228
228
  readonly browserCascadeEquivalenceClaim?: true;
229
229
  readonly cssCascadeRuntimeProofs?: readonly CssCascadeRuntimeProofRecord[];
230
+ readonly cssDependencyGraphProofs?: readonly CssDependencyGraphProofRecord[];
230
231
  }
231
232
 
232
233
  export interface CssSafeMergeResult {
@@ -242,6 +243,7 @@ export interface CssSafeMergeResult {
242
243
  readonly workerChangedCssModuleContracts?: number; readonly headChangedCssModuleContracts?: number;
243
244
  readonly parserEvidence?: CssSafeMergeParserEvidence; readonly selectorTargetEvidence?: CssSafeMergeSelectorTargetEvidence; readonly dependencyGraphEvidence?: CssDependencyGraphEvidence;
244
245
  readonly cascadeRuntimeProofs?: readonly CssCascadeRuntimeProofRecord[];
246
+ readonly dependencyGraphProofs?: readonly CssDependencyGraphProofRecord[];
245
247
  }
246
248
 
247
249
  export interface CssSafeMergeParserEvidence {
@@ -271,15 +273,15 @@ export interface CssSafeMergeSelectorTargetSideEvidence {
271
273
  export interface CssSafeMergeSelectorMove {
272
274
  readonly side: string; readonly property: string; readonly beforeRuleKey: string; readonly afterRuleKey: string;
273
275
  readonly beforeSelectors?: readonly string[]; readonly afterSelectors?: readonly string[]; readonly beforeScopes?: readonly string[]; readonly afterScopes?: readonly string[];
274
- readonly declarationHash: string; readonly beforeSelectorTargetGraphHash?: string; readonly afterSelectorTargetGraphHash?: string; readonly selectorTargetGraphHashPresent: boolean;
276
+ readonly beforeSpecificity?: readonly (readonly number[])[]; readonly afterSpecificity?: readonly (readonly number[])[]; readonly specificityInvariant: boolean; readonly declarationHash: string; readonly beforeSelectorTargetGraphHash?: string; readonly afterSelectorTargetGraphHash?: string; readonly selectorTargetGraphHashPresent: boolean;
275
277
  }
276
278
 
277
279
  export interface CssSafeMergeSelectorTargetRebaseProof {
278
- readonly kind: 'css-selector-target-rebase'; readonly side: string; readonly fromRuleKey: string; readonly toRuleKey: string; readonly property: string; readonly cascadeKey: string;
280
+ readonly kind: 'css-selector-target-rebase'; readonly side: string; readonly fromRuleKey: string; readonly toRuleKey: string; readonly property: string; readonly cascadeKey: string; readonly selectorTargetGraphHash?: string; readonly specificityInvariant: true; readonly beforeSpecificity?: readonly (readonly number[])[]; readonly afterSpecificity?: readonly (readonly number[])[];
279
281
  }
280
282
 
281
283
  export interface CssSelectorTargetEquivalence {
282
- readonly fromRuleKey?: string; readonly toRuleKey?: string; readonly fromSelectors?: readonly string[]; readonly toSelectors?: readonly string[]; readonly graphHash?: string;
284
+ readonly sourcePath?: string; readonly fromRuleKey?: string; readonly toRuleKey?: string; readonly fromSelectors?: readonly string[]; readonly toSelectors?: readonly string[]; readonly fromSpecificity?: readonly (readonly number[])[]; readonly toSpecificity?: readonly (readonly number[])[]; readonly graphHash?: string;
283
285
  }
284
286
 
285
287
  export interface CssSafeMergeInput {
@@ -295,6 +297,8 @@ export interface CssSafeMergeInput {
295
297
  readonly cascadeRuntimeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
296
298
  readonly sourceBoundCascadeProof?: CssCascadeRuntimeProof; readonly sourceBoundCascadeProofs?: readonly CssCascadeRuntimeProof[];
297
299
  readonly sourceBoundCascadeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
300
+ readonly cssDependencyGraphProof?: CssDependencyGraphProof; readonly cssDependencyGraphProofs?: readonly CssDependencyGraphProof[]; readonly cssSourceBoundDependencyGraphProof?: CssDependencyGraphProof; readonly cssSourceBoundDependencyGraphProofs?: readonly CssDependencyGraphProof[];
301
+ readonly cssDependencyGraphProofsByPath?: Readonly<Record<string, CssDependencyGraphProof | readonly CssDependencyGraphProof[]>>; readonly cssSourceBoundDependencyGraphProofsByPath?: Readonly<Record<string, CssDependencyGraphProof | readonly CssDependencyGraphProof[]>>;
298
302
  readonly selectorTargetGraphHash?: string; readonly selectorTargetEquivalences?: readonly CssSelectorTargetEquivalence[];
299
303
  readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
300
304
  readonly baseGeneratedClassNameMapHash?: string; readonly workerGeneratedClassNameMapHash?: string; readonly headGeneratedClassNameMapHash?: string;
@@ -45,6 +45,9 @@ function selectorTargetMoves(changes, side) {
45
45
  afterSelectors: addition.after.selectors,
46
46
  beforeScopes: deletion.before.scopes,
47
47
  afterScopes: addition.after.scopes,
48
+ beforeSpecificity: deletion.before.specificity,
49
+ afterSpecificity: addition.after.specificity,
50
+ specificityInvariant: specificityListKey(deletion.before.specificity) === specificityListKey(addition.after.specificity),
48
51
  declarationHash: addition.after.declarationHash,
49
52
  beforeSelectorTargetGraphHash: deletion.before.selectorTargetGraphHash,
50
53
  afterSelectorTargetGraphHash: addition.after.selectorTargetGraphHash,
@@ -73,7 +76,7 @@ function selectorTargetMoveSidePlan(id, sourcePath, moves, oppositeMoves, opposi
73
76
  for (let index = 0; index < oppositeChanges.length; index += 1) {
74
77
  const change = oppositeChanges[index];
75
78
  if (!selectorMoveTouchesChange(move, change)) continue;
76
- if (canRebaseChange(move, change, options)) {
79
+ if (canRebaseChange(move, change, options, sourcePath)) {
77
80
  const rebased = rebaseChangeToSelectorMove(change, move);
78
81
  oppositeChanges[index] = rebased.change;
79
82
  rebaseProofs.push(rebased.proof);
@@ -83,16 +86,19 @@ function selectorTargetMoveSidePlan(id, sourcePath, moves, oppositeMoves, opposi
83
86
  return { conflicts, rebaseProofs };
84
87
  }
85
88
 
86
- function canRebaseChange(move, change, options) {
87
- return change.kind === 'add' && change.after && hasSelectorTargetEquivalence(move, options);
89
+ function canRebaseChange(move, change, options, sourcePath) {
90
+ return change.kind === 'add' && change.after && hasSelectorTargetEquivalence(move, options, sourcePath);
88
91
  }
89
92
 
90
- function hasSelectorTargetEquivalence(move, options) {
93
+ function hasSelectorTargetEquivalence(move, options, sourcePath) {
94
+ if (!move.specificityInvariant) return false;
91
95
  return (options.selectorTargetEquivalences ?? []).some((entry) => {
96
+ const sourceMatches = !entry.sourcePath || entry.sourcePath === sourcePath;
92
97
  const ruleKeysMatch = entry.fromRuleKey === move.beforeRuleKey && entry.toRuleKey === move.afterRuleKey;
93
98
  const selectorsMatch = selectorListKey(entry.fromSelectors) === selectorListKey(move.beforeSelectors) && selectorListKey(entry.toSelectors) === selectorListKey(move.afterSelectors);
94
- const graphMatches = !entry.graphHash || entry.graphHash === move.beforeSelectorTargetGraphHash || entry.graphHash === move.afterSelectorTargetGraphHash;
95
- return graphMatches && (ruleKeysMatch || selectorsMatch);
99
+ const graphMatches = Boolean(entry.graphHash && entry.graphHash === move.beforeSelectorTargetGraphHash && entry.graphHash === move.afterSelectorTargetGraphHash);
100
+ const specificityMatches = (!entry.fromSpecificity || specificityListKey(entry.fromSpecificity) === specificityListKey(move.beforeSpecificity)) && (!entry.toSpecificity || specificityListKey(entry.toSpecificity) === specificityListKey(move.afterSpecificity));
101
+ return sourceMatches && graphMatches && specificityMatches && (ruleKeysMatch || selectorsMatch);
96
102
  });
97
103
  }
98
104
 
@@ -100,7 +106,7 @@ function rebaseChangeToSelectorMove(change, move) {
100
106
  const after = { ...change.after, ruleKey: move.afterRuleKey, selectors: move.afterSelectors, scopes: move.afterScopes ?? [], key: cascadeKey(move.afterScopes, move.afterSelectors, change.after.property), rebasedFromRuleKey: move.beforeRuleKey };
101
107
  return {
102
108
  change: { ...change, key: after.key, after },
103
- proof: { kind: 'css-selector-target-rebase', side: change.side, fromRuleKey: move.beforeRuleKey, toRuleKey: move.afterRuleKey, property: change.after.property, cascadeKey: after.key }
109
+ proof: { kind: 'css-selector-target-rebase', side: change.side, fromRuleKey: move.beforeRuleKey, toRuleKey: move.afterRuleKey, property: change.after.property, cascadeKey: after.key, selectorTargetGraphHash: move.afterSelectorTargetGraphHash, specificityInvariant: true, beforeSpecificity: move.beforeSpecificity, afterSpecificity: move.afterSpecificity }
104
110
  };
105
111
  }
106
112
 
@@ -134,6 +140,7 @@ function sameSelectorMove(left, right) {
134
140
 
135
141
  function cascadeKey(scopes = [], selectors = [], property) { return [...scopes, selectors.join(','), property].join('::'); }
136
142
  function selectorListKey(value = []) { return Array.isArray(value) ? value.join(',') : undefined; }
143
+ function specificityListKey(value = []) { return Array.isArray(value) ? value.map((item) => Array.isArray(item) ? item.join(',') : '').join('|') : undefined; }
137
144
  function changeDetails(change) { return { kind: change.kind, property: (change.after ?? change.before)?.property, value: change.after?.value, important: change.after?.important }; }
138
145
 
139
146
  export { mergeSelectorTargetEvidence, planSelectorTargetRebase };
@@ -1,6 +1,6 @@
1
1
  import { cssModuleContractChanges, cssModuleContractConflicts, sheetOptions, unsupportedSourceShapeChanges } from './semantic-merge-css-modules.js';
2
2
  import { admitCascadeRuntimeProofs } from './semantic-merge-cascade-runtime.js';
3
- import { mergeCssDependencyGraphEvidence } from './dependency-graph.js';
3
+ import { admitCssDependencyGraphProofs, mergeCssDependencyGraphEvidence } from './dependency-graph.js';
4
4
  import { mergeSelectorTargetEvidence, planSelectorTargetRebase } from './semantic-merge-selector-targets.js';
5
5
  import { applyAtRuleBlockChanges, atRuleBlockEntry, atRuleBlockOverlapConflicts, atRuleOccurrenceKey, changedAtRuleBlocks, renderAtRuleBlock, renderAtRuleStatement } from './semantic-merge-at-rules.js';
6
6
  import { declarationsOverlapByCssProperty, shorthandGroupForProperty } from './semantic-merge-shorthand.js';
@@ -52,8 +52,9 @@ function safeMergeCssSource(input = {}, context = {}) {
52
52
  binding: { base, worker, head, output: mergedSourceText },
53
53
  hash
54
54
  });
55
- const conflicts = [...parserConflicts, ...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...cascadeRuntimeAdmission.conflicts, ...selectorTargetPlan.conflicts];
56
- if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, dependencyGraphEvidence, selectorTargetEvidence: selectorTargetPlan.evidence, cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs });
55
+ const dependencyGraphAdmission = admitCssDependencyGraphProofs({ id, sourcePath, input, dependencyGraphEvidence, binding: { base, worker, head, output: mergedSourceText }, hash });
56
+ const conflicts = [...parserConflicts, ...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...cascadeRuntimeAdmission.conflicts, ...dependencyGraphAdmission.conflicts, ...selectorTargetPlan.conflicts];
57
+ if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, dependencyGraphEvidence, selectorTargetEvidence: selectorTargetPlan.evidence, cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs, dependencyGraphProofs: dependencyGraphAdmission.proofs });
57
58
  return merged(id, sourcePath, mergedSourceText, 'semantic-declaration-merge', hash, {
58
59
  baseSheetHash: sheets.base.sheetHash,
59
60
  workerSheetHash: sheets.worker.sheetHash,
@@ -66,6 +67,7 @@ function safeMergeCssSource(input = {}, context = {}) {
66
67
  dependencyGraphEvidence,
67
68
  selectorTargetEvidence: selectorTargetPlan.evidence,
68
69
  cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs,
70
+ dependencyGraphProofs: dependencyGraphAdmission.proofs,
69
71
  browserCascadeEquivalenceClaim: cascadeRuntimeAdmission.proofs.length > 0
70
72
  });
71
73
  }
@@ -99,7 +101,7 @@ function declarationIndex(sheet) {
99
101
  key: declaration.cascadeKey,
100
102
  ruleKey,
101
103
  selectors: record.selectors,
102
- scopes: record.scopes ?? [],
104
+ scopes: record.scopes ?? [], specificity: record.specificity,
103
105
  property: declaration.property,
104
106
  value: declaration.value,
105
107
  important: declaration.important,
@@ -297,7 +299,7 @@ function result(id, sourcePath, status, body) {
297
299
  reviewRequired: status !== 'merged',
298
300
  reasonCodes: unique((body.conflicts ?? []).map((item) => item.details.reasonCode)),
299
301
  browserCascadeEquivalenceClaim: browserCascadeEquivalenceClaim || undefined,
300
- cssCascadeRuntimeProofs: body.cascadeRuntimeProofs?.length ? body.cascadeRuntimeProofs : undefined
302
+ cssCascadeRuntimeProofs: body.cascadeRuntimeProofs?.length ? body.cascadeRuntimeProofs : undefined, cssDependencyGraphProofs: body.dependencyGraphProofs?.length ? body.dependencyGraphProofs : undefined
301
303
  }
302
304
  };
303
305
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-css",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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",