@shapeshift-labs/frontier-lang-css 0.1.3 → 0.1.4

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, cascade keys, CSS Modules exports, ICSS edges, source spans, stable hashes, and fail-closed proof gaps for cascade/render-sensitive CSS surfaces. `safeMergeCssSource` admits independent unscoped declaration edits by cascade key, and for `.module.css` files it classifies exported local classes, `composes`, and ICSS import/export records as explicit merge contracts.
235
+ `sourceMap.mappings` links emitted rule blocks back to Frontier Lang semantic node ids. `createCssSemanticMergeEvidence` records selectors, specificity, declarations, custom properties, cascade keys, 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, and for `.module.css` files it classifies exported local classes, `composes`, and ICSS import/export records as explicit merge contracts.
236
236
 
237
237
  ## Support Boundary
238
238
 
239
- - Ready evidence: style rules, selectors, specificity, declarations, custom properties, CSS Modules local exports, generated class-name map coverage, JS/TS use-site graph hashes, composition graph hashes, ICSS graph hashes, source spans, stable hashes.
240
- - Safe merge: independent unscoped declarations with non-overlapping cascade keys; 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, shorthands without longhand expansion, scoped cascade under `@media` / `@supports` / `@container` / `@layer`, `@keyframes`, `@font-face`, `@page`, browser layout and render equivalence.
239
+ - Ready evidence: style rules, selectors, specificity, declarations, custom properties, 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 spans, stable hashes.
240
+ - Safe merge: independent declarations with non-overlapping cascade keys; existing scoped declaration edits when scoped cascade graph 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, shorthands without longhand expansion, unproved or structurally changed scoped cascade under `@media` / `@supports` / `@container` / `@layer`, `@keyframes`, `@font-face`, `@page`, browser layout and render equivalence.
242
242
  - Claims: `autoMergeClaim`, `semanticEquivalenceClaim`, `browserCascadeEquivalenceClaim`, and `browserRenderEquivalenceClaim` remain false.
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface CssProjectionOptions {
12
12
  readonly jsTsUseSiteGraphHash?: string;
13
13
  readonly cssModuleCompositionGraphHash?: string;
14
14
  readonly icssGraphHash?: string;
15
+ readonly scopedCascadeGraphHash?: string;
15
16
  readonly targetPath?: string;
16
17
  readonly semanticIndexId?: string;
17
18
  readonly sourceSpansBySemanticNodeId?: Readonly<Record<string, CssSourceSpan>>;
@@ -124,6 +125,7 @@ export interface CssSemanticRecord {
124
125
  readonly atRuleName?: string;
125
126
  readonly conditionText?: string;
126
127
  readonly scopeKey?: string;
128
+ readonly scopedCascadeGraphHash?: string;
127
129
  readonly sourceSpan: CssSourceSpan;
128
130
  readonly sourceHash: string;
129
131
  readonly ruleHash?: string;
@@ -266,6 +268,7 @@ export interface CssSafeMergeInput {
266
268
  readonly jsTsUseSiteGraphHash?: string;
267
269
  readonly cssModuleCompositionGraphHash?: string;
268
270
  readonly icssGraphHash?: string;
271
+ readonly scopedCascadeGraphHash?: string;
269
272
  readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>;
270
273
  readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>;
271
274
  readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
@@ -281,6 +284,9 @@ export interface CssSafeMergeInput {
281
284
  readonly baseIcssGraphHash?: string;
282
285
  readonly workerIcssGraphHash?: string;
283
286
  readonly headIcssGraphHash?: string;
287
+ readonly baseScopedCascadeGraphHash?: string;
288
+ readonly workerScopedCascadeGraphHash?: string;
289
+ readonly headScopedCascadeGraphHash?: string;
284
290
  }
285
291
 
286
292
  export declare function toCssAst(document: FrontierLangDocument, options?: CssProjectionOptions): CssAstStylesheet;
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ export function emitCssWithSourceMap(document, options = {}) {
59
59
  export function parseCssSemanticSheet(sourceText, options = {}) {
60
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);
62
+ const records = parseCssBlocks(sourceText, 0, sourceText.length, [], lineStarts, sourceHash, options);
63
63
  const cssModules = createCssModuleEvidence(records, options, sourceHash);
64
64
  const proofGaps = [
65
65
  ...records.flatMap((record) => record.proofGaps ?? []),
@@ -112,7 +112,7 @@ export function safeMergeCssSource(input = {}) {
112
112
  return safeMergeCssSourceImpl(input, { parseCssSemanticSheet, hashSemanticValue });
113
113
  }
114
114
 
115
- function parseCssBlocks(sourceText, start, end, scopes, lineStarts, sourceHash) {
115
+ function parseCssBlocks(sourceText, start, end, scopes, lineStarts, sourceHash, options) {
116
116
  const records = [];
117
117
  let index = start;
118
118
  while (index < end) {
@@ -124,23 +124,23 @@ function parseCssBlocks(sourceText, start, end, scopes, lineStarts, sourceHash)
124
124
  const prelude = sourceText.slice(preludeStart, open).replace(/\/\*[\s\S]*?\*\//g, '').trim();
125
125
  const body = sourceText.slice(open + 1, close);
126
126
  if (prelude.startsWith('@')) {
127
- const at = parseAtRule(prelude, preludeStart, close + 1, lineStarts, sourceHash, scopes);
127
+ const at = parseAtRule(prelude, preludeStart, close + 1, lineStarts, sourceHash, scopes, options);
128
128
  records.push(at);
129
- if (ScopeAtRules.has(at.atRuleName)) records.push(...parseCssBlocks(sourceText, open + 1, close, [...scopes, at.scopeKey], lineStarts, sourceHash));
129
+ if (ScopeAtRules.has(at.atRuleName)) records.push(...parseCssBlocks(sourceText, open + 1, close, [...scopes, at.scopeKey], lineStarts, sourceHash, options));
130
130
  } else if (prelude) {
131
- records.push(cssRuleRecord(prelude, body, preludeStart, close + 1, lineStarts, sourceHash, scopes));
131
+ records.push(cssRuleRecord(prelude, body, preludeStart, close + 1, lineStarts, sourceHash, scopes, options));
132
132
  }
133
133
  index = close + 1;
134
134
  }
135
135
  return records;
136
136
  }
137
137
 
138
- function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes) {
138
+ function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes, options) {
139
139
  const selectors = prelude.split(',').map((selector) => selector.trim()).filter(Boolean);
140
140
  const declarations = parseDeclarations(body);
141
141
  const proofGaps = [
142
142
  ...declarations.filter((declaration) => ShorthandProperties.has(declaration.property)).map((declaration) => proofGap('css-shorthand-expansion-unproved', `CSS shorthand ${declaration.property} needs longhand expansion evidence.`)),
143
- ...scopes.length ? [proofGap('css-scoped-cascade-equivalence-unproved', 'Scoped cascade equivalence requires browser/style evidence.')] : []
143
+ ...scopes.length && !options.scopedCascadeGraphHash ? [proofGap('css-scoped-cascade-equivalence-unproved', 'Scoped cascade equivalence requires browser/style evidence.')] : []
144
144
  ];
145
145
  return compactRecord({
146
146
  kind: 'rule',
@@ -155,6 +155,7 @@ function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes
155
155
  declarationHash: hashSemanticValue({ kind: 'frontier.lang.css.declaration.v1', scopes, selectors, declaration })
156
156
  })),
157
157
  customProperties: declarations.filter((declaration) => declaration.property.startsWith('--')).map((declaration) => declaration.property),
158
+ scopedCascadeGraphHash: scopes.length ? options.scopedCascadeGraphHash : undefined,
158
159
  sourceSpan: sourceSpan(start, end, lineStarts),
159
160
  sourceHash,
160
161
  ruleHash: hashSemanticValue({ kind: 'frontier.lang.css.rule.v1', selectors, scopes, declarations }),
@@ -162,19 +163,20 @@ function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes
162
163
  });
163
164
  }
164
165
 
165
- function parseAtRule(prelude, start, end, lineStarts, sourceHash, scopes) {
166
+ function parseAtRule(prelude, start, end, lineStarts, sourceHash, scopes, options) {
166
167
  const match = /^@([A-Za-z-]+)\s*([\s\S]*)$/.exec(prelude);
167
168
  const atRuleName = match?.[1]?.toLowerCase() ?? 'unknown';
168
169
  const conditionText = match?.[2]?.trim() ?? '';
169
170
  const proofGaps = [];
170
171
  if (RuntimeAtRules.has(atRuleName)) proofGaps.push(proofGap(`css-${atRuleName}-runtime-equivalence-unproved`, `CSS @${atRuleName} semantics require browser evidence.`));
171
- if (ScopeAtRules.has(atRuleName)) proofGaps.push(proofGap(`css-${atRuleName}-cascade-scope-unproved`, `CSS @${atRuleName} scoped cascade requires condition evaluation evidence.`));
172
+ if (ScopeAtRules.has(atRuleName) && !options.scopedCascadeGraphHash) proofGaps.push(proofGap(`css-${atRuleName}-cascade-scope-unproved`, `CSS @${atRuleName} scoped cascade requires condition evaluation evidence.`));
172
173
  return compactRecord({
173
174
  kind: 'at-rule',
174
175
  atRuleName,
175
176
  conditionText,
176
177
  scopeKey: `@${atRuleName} ${conditionText}`.trim(),
177
178
  scopes,
179
+ scopedCascadeGraphHash: ScopeAtRules.has(atRuleName) ? options.scopedCascadeGraphHash : undefined,
178
180
  sourceSpan: sourceSpan(start, end, lineStarts),
179
181
  sourceHash,
180
182
  atRuleHash: hashSemanticValue({ kind: 'frontier.lang.css.atRule.v1', atRuleName, conditionText, scopes }),
@@ -8,7 +8,8 @@ function sheetOptions(input, side, sourcePath) {
8
8
  generatedClassNameMapHash: input[`${prefix}GeneratedClassNameMapHash`] ?? input.generatedClassNameMapHash,
9
9
  jsTsUseSiteGraphHash: input[`${prefix}JsTsUseSiteGraphHash`] ?? input.jsTsUseSiteGraphHash,
10
10
  cssModuleCompositionGraphHash: input[`${prefix}CssModuleCompositionGraphHash`] ?? input.cssModuleCompositionGraphHash,
11
- icssGraphHash: input[`${prefix}IcssGraphHash`] ?? input.icssGraphHash
11
+ icssGraphHash: input[`${prefix}IcssGraphHash`] ?? input.icssGraphHash,
12
+ scopedCascadeGraphHash: input[`${prefix}ScopedCascadeGraphHash`] ?? input.scopedCascadeGraphHash
12
13
  };
13
14
  }
14
15
 
@@ -150,18 +150,33 @@ function renderDeclarationIndex(index) {
150
150
  const groups = new Map();
151
151
  for (const key of index.order) {
152
152
  const declaration = index.declarations.get(key);
153
- if (!declaration || declaration.scopes.length) continue;
153
+ if (!declaration) continue;
154
154
  groups.set(declaration.ruleKey, [...(groups.get(declaration.ruleKey) ?? []), declaration]);
155
155
  }
156
156
  const chunks = [];
157
157
  for (const declarations of groups.values()) {
158
- chunks.push(`${declarations[0].selectors.join(', ')} {`);
159
- for (const declaration of declarations) chunks.push(` ${declaration.property}: ${declaration.value};`);
160
- chunks.push('}', '');
158
+ renderDeclarationGroup(chunks, declarations);
161
159
  }
162
160
  return `${chunks.join('\n').trimEnd()}\n`;
163
161
  }
164
162
 
163
+ function renderDeclarationGroup(chunks, declarations) {
164
+ const first = declarations[0];
165
+ let indent = 0;
166
+ for (const scope of first.scopes) {
167
+ chunks.push(`${spaces(indent)}${scope} {`);
168
+ indent += 2;
169
+ }
170
+ chunks.push(`${spaces(indent)}${first.selectors.join(', ')} {`);
171
+ for (const declaration of declarations) chunks.push(`${spaces(indent + 2)}${declaration.property}: ${declaration.value};`);
172
+ chunks.push(`${spaces(indent)}}`);
173
+ for (let index = first.scopes.length - 1; index >= 0; index -= 1) {
174
+ indent -= 2;
175
+ chunks.push(`${spaces(indent)}}`);
176
+ }
177
+ chunks.push('');
178
+ }
179
+
165
180
  function merged(id, sourcePath, sourceText, operation, hash, extra = {}) {
166
181
  return result(id, sourcePath, 'merged', {
167
182
  operation,
@@ -209,6 +224,7 @@ function proofGapsForDeclaration(record, declaration) {
209
224
  return (record.proofGaps ?? []).filter((gap) => gap.code !== 'css-shorthand-expansion-unproved' || gap.summary.includes(` ${declaration.property} `));
210
225
  }
211
226
  function unique(values) { return [...new Set(values.filter(Boolean))]; }
227
+ function spaces(count) { return ' '.repeat(Math.max(0, count)); }
212
228
 
213
229
  function shorthandGroupForProperty(property) {
214
230
  if (ShorthandGroups.has(property)) return property;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-css",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",