domflax 0.1.0 → 0.1.1

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.
Files changed (39) hide show
  1. package/README.md +159 -0
  2. package/dist/{chunk-4HHISSMR.js → chunk-DNHOGPYV.js} +2675 -1503
  3. package/dist/chunk-DNHOGPYV.js.map +1 -0
  4. package/dist/{chunk-ZJ2S36GY.js → chunk-DOQEBGWB.js} +33 -20
  5. package/dist/chunk-DOQEBGWB.js.map +1 -0
  6. package/dist/{chunk-77SLHRN6.js → chunk-DWLB7FRR.js} +341 -176
  7. package/dist/chunk-DWLB7FRR.js.map +1 -0
  8. package/dist/cli.cjs +2169 -760
  9. package/dist/cli.cjs.map +1 -1
  10. package/dist/cli.js +183 -91
  11. package/dist/cli.js.map +1 -1
  12. package/dist/index.cjs +3021 -1699
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +477 -54
  15. package/dist/index.d.ts +477 -54
  16. package/dist/index.js +49 -3
  17. package/dist/pattern-CV607P87.d.ts +547 -0
  18. package/dist/pattern-F5xBtIE-.d.cts +547 -0
  19. package/dist/pattern-kit.cjs +60 -39
  20. package/dist/pattern-kit.cjs.map +1 -1
  21. package/dist/pattern-kit.d.cts +3 -18
  22. package/dist/pattern-kit.d.ts +3 -18
  23. package/dist/pattern-kit.js +3 -1
  24. package/dist/pattern-kit.js.map +1 -1
  25. package/dist/{types-BQ7l6dVe.d.ts → resolve-ops-DIwEelH-.d.cts} +26 -251
  26. package/dist/{types-BQ7l6dVe.d.cts → resolve-ops-DIwEelH-.d.ts} +26 -251
  27. package/dist/verify.d.cts +1 -1
  28. package/dist/verify.d.ts +1 -1
  29. package/dist/webpack-loader.cjs +2975 -1699
  30. package/dist/webpack-loader.cjs.map +1 -1
  31. package/dist/webpack-loader.d.cts +2 -2
  32. package/dist/webpack-loader.d.ts +2 -2
  33. package/dist/webpack-loader.js +3 -3
  34. package/package.json +3 -6
  35. package/dist/chunk-4HHISSMR.js.map +0 -1
  36. package/dist/chunk-77SLHRN6.js.map +0 -1
  37. package/dist/chunk-ZJ2S36GY.js.map +0 -1
  38. package/dist/pattern-CX6iBzTD.d.ts +0 -237
  39. package/dist/pattern-P4FIKAUB.d.cts +0 -237
package/dist/cli.cjs CHANGED
@@ -176,8 +176,7 @@ init_cjs_shims();
176
176
  // ../cli/src/index.ts
177
177
  init_cjs_shims();
178
178
  var import_node_fs2 = require("fs");
179
- var path5 = __toESM(require("path"), 1);
180
- var import_node_url = require("url");
179
+ var path6 = __toESM(require("path"), 1);
181
180
 
182
181
  // ../cli/src/options.ts
183
182
  init_cjs_shims();
@@ -507,6 +506,9 @@ function elementIds(doc) {
507
506
 
508
507
  // ../core/src/ops.ts
509
508
  init_cjs_shims();
509
+
510
+ // ../core/src/ops/runtime.ts
511
+ init_cjs_shims();
510
512
  function cloneStyleMap(sm) {
511
513
  const blocks = /* @__PURE__ */ new Map();
512
514
  for (const [key, block] of sm.blocks) {
@@ -573,6 +575,32 @@ function removeSubtree(state, id) {
573
575
  state.doc.nodes.delete(id);
574
576
  state.removed.add(id);
575
577
  }
578
+ function precond(op, nodeId, message) {
579
+ return diag("DF_OP_PRECONDITION_FAILED", message, {
580
+ nodeId,
581
+ pattern: op.origin.pattern,
582
+ severity: "error"
583
+ });
584
+ }
585
+ function primaryTarget(op) {
586
+ switch (op.op) {
587
+ case "removeNode":
588
+ case "unwrap":
589
+ case "replaceWith":
590
+ case "wrap":
591
+ case "moveNode":
592
+ case "setClassList":
593
+ case "mergeStyle":
594
+ return op.target;
595
+ case "insertBefore":
596
+ case "insertAfter":
597
+ return op.anchor;
598
+ case "mergeSiblings":
599
+ return op.first;
600
+ case "foldInheritedStyles":
601
+ return op.from;
602
+ }
603
+ }
576
604
  function specToAttrs(map) {
577
605
  if (!map || map.size === 0) return { entries: /* @__PURE__ */ new Map(), spreads: [], order: [] };
578
606
  const entries = /* @__PURE__ */ new Map();
@@ -681,6 +709,9 @@ function mergeStyleMaps(target, source, policy) {
681
709
  }
682
710
  return { map: { blocks }, conflict };
683
711
  }
712
+
713
+ // ../core/src/ops/apply.ts
714
+ init_cjs_shims();
684
715
  function safetyOk(state, op, targetId) {
685
716
  const opSafety = op.origin.safety;
686
717
  if (opSafety > state.ceiling) {
@@ -907,32 +938,6 @@ function applyFold(state, op) {
907
938
  for (const d3 of issues) state.diagnostics.push(d3);
908
939
  return [];
909
940
  }
910
- function precond(op, nodeId, message) {
911
- return diag("DF_OP_PRECONDITION_FAILED", message, {
912
- nodeId,
913
- pattern: op.origin.pattern,
914
- severity: "error"
915
- });
916
- }
917
- function primaryTarget(op) {
918
- switch (op.op) {
919
- case "removeNode":
920
- case "unwrap":
921
- case "replaceWith":
922
- case "wrap":
923
- case "moveNode":
924
- case "setClassList":
925
- case "mergeStyle":
926
- return op.target;
927
- case "insertBefore":
928
- case "insertAfter":
929
- return op.anchor;
930
- case "mergeSiblings":
931
- return op.first;
932
- case "foldInheritedStyles":
933
- return op.from;
934
- }
935
- }
936
941
  function applyOps(doc, ops, ctx) {
937
942
  const cloned = cloneDocument(doc);
938
943
  const state = {
@@ -974,13 +979,9 @@ function finalize(state) {
974
979
 
975
980
  // ../core/src/pass-manager.ts
976
981
  init_cjs_shims();
977
- var DEFAULT_FIXPOINT = {
978
- maxIterations: 16,
979
- phases: { flatten: 16, compress: 8, extract: 4 },
980
- onBudgetExhausted: "warn",
981
- detectOscillation: true
982
- };
983
- var PHASE_ORDER = ["flatten", "compress", "extract"];
982
+
983
+ // ../core/src/pass-context.ts
984
+ init_cjs_shims();
984
985
  function createNullSelectorIndex() {
985
986
  const none = /* @__PURE__ */ new Set();
986
987
  return {
@@ -1190,10 +1191,137 @@ function buildMatchContext(doc, elementId, resolver, selectors, safety, phase, i
1190
1191
  }
1191
1192
  };
1192
1193
  }
1193
- function stampOrigin(draft, pattern2) {
1194
+
1195
+ // ../core/src/flatten-safety.ts
1196
+ init_cjs_shims();
1197
+ var DISPLAY = "display";
1198
+ var POSITION = "position";
1199
+ var TRANSFORM = "transform";
1200
+ var CONTEXT_SAFE_DISPLAYS = /* @__PURE__ */ new Set(["block", "contents", ""]);
1201
+ var STATIC_POSITIONS = /* @__PURE__ */ new Set(["static", ""]);
1202
+ var SELF_ALIGN_PROPS = [
1203
+ "place-self",
1204
+ "align-self",
1205
+ "justify-self"
1206
+ ];
1207
+ var NEUTRAL_ALIGN = /* @__PURE__ */ new Set(["auto", "normal", "auto auto", ""]);
1208
+ var FLEX_GRID_DISPLAYS = /* @__PURE__ */ new Set(["flex", "inline-flex", "grid", "inline-grid"]);
1209
+ function establishesChildContext(sm) {
1210
+ for (const block of sm.blocks.values()) {
1211
+ const display = block.decls.get(DISPLAY);
1212
+ if (display && !CONTEXT_SAFE_DISPLAYS.has(String(display.value))) return true;
1213
+ const position = block.decls.get(POSITION);
1214
+ if (position && !STATIC_POSITIONS.has(String(position.value))) return true;
1215
+ const transform = block.decls.get(TRANSFORM);
1216
+ if (transform && String(transform.value) !== "none") return true;
1217
+ }
1218
+ return false;
1219
+ }
1220
+ function isInherited2(decl, norm) {
1221
+ if (decl.inherited) return true;
1222
+ try {
1223
+ return norm.inherited.isInherited(decl.property);
1224
+ } catch {
1225
+ return false;
1226
+ }
1227
+ }
1228
+ function childReproduces(childComputed, conditionK, prop, value) {
1229
+ if (!childComputed) return false;
1230
+ const block = childComputed.blocks.get(conditionK);
1231
+ if (!block) return false;
1232
+ const d3 = block.decls.get(prop);
1233
+ return d3 != null && String(d3.value) === value;
1234
+ }
1235
+ function dropsOwnStyle(wrapperComputed, childComputed, norm) {
1236
+ for (const block of wrapperComputed.blocks.values()) {
1237
+ const ck = conditionKey(block.condition);
1238
+ for (const [prop, decl] of block.decls) {
1239
+ if (prop === DISPLAY || prop === POSITION || prop === TRANSFORM) continue;
1240
+ if (isInherited2(decl, norm)) continue;
1241
+ if (!childReproduces(childComputed, ck, prop, String(decl.value))) return true;
1242
+ }
1243
+ }
1244
+ return false;
1245
+ }
1246
+ function displayOf(el, norm) {
1247
+ if (!el) return "";
1248
+ for (const block of norm.normalizeStyleMap(el.computed).blocks.values()) {
1249
+ if (block.condition.media === "" && block.condition.states.length === 0 && block.condition.pseudoElement === "") {
1250
+ const d3 = block.decls.get(DISPLAY);
1251
+ if (d3) return String(d3.value);
1252
+ }
1253
+ }
1254
+ return "";
1255
+ }
1256
+ function newParentIsFlexOrGrid(before, wrapper, norm) {
1257
+ if (wrapper.parent == null) return false;
1258
+ const p2 = before.nodes.get(wrapper.parent);
1259
+ if (!p2 || p2.kind !== "element") return false;
1260
+ return FLEX_GRID_DISPLAYS.has(displayOf(p2, norm));
1261
+ }
1262
+ function addsParentContextStyle(childBefore, childAfter, norm) {
1263
+ if (!childAfter) return false;
1264
+ const before = childBefore ? norm.normalizeStyleMap(childBefore) : null;
1265
+ const after = norm.normalizeStyleMap(childAfter);
1266
+ for (const block of after.blocks.values()) {
1267
+ const ck = conditionKey(block.condition);
1268
+ for (const prop of SELF_ALIGN_PROPS) {
1269
+ const d3 = block.decls.get(prop);
1270
+ if (!d3 || NEUTRAL_ALIGN.has(String(d3.value))) continue;
1271
+ const prev = before?.blocks.get(ck)?.decls.get(prop);
1272
+ if (!prev || String(prev.value) !== String(d3.value)) return true;
1273
+ }
1274
+ }
1275
+ return false;
1276
+ }
1277
+ function unwrapTargetOf(ops) {
1278
+ for (const op of ops) if (op.op === "unwrap") return op.target;
1279
+ return null;
1280
+ }
1281
+ function survivingChildOf(ops, wrapper, before) {
1282
+ for (const op of ops) if (op.op === "mergeStyle") return op.target;
1283
+ for (const op of ops) if (op.op === "foldInheritedStyles" && op.into.length > 0) return op.into[0];
1284
+ for (const c of wrapper.children) {
1285
+ const n = before.nodes.get(c);
1286
+ if (n && n.kind === "element") return c;
1287
+ }
1288
+ return null;
1289
+ }
1290
+ function classifyFlattenOps(before, after, ops, norm) {
1291
+ const wrapperId = unwrapTargetOf(ops);
1292
+ if (wrapperId == null) return { kind: "provably-safe", wrapperId: null, childId: null };
1293
+ const wrapper = before.nodes.get(wrapperId);
1294
+ if (!wrapper || wrapper.kind !== "element") {
1295
+ return { kind: "provably-safe", wrapperId: null, childId: null };
1296
+ }
1297
+ const childId = survivingChildOf(ops, wrapper, before);
1298
+ const wrapperComputed = norm.normalizeStyleMap(wrapper.computed);
1299
+ const childAfter = childId != null ? getElement(after, childId)?.computed ?? null : null;
1300
+ const childBefore = childId != null ? getElement(before, childId)?.computed ?? null : null;
1301
+ if (establishesChildContext(wrapperComputed)) {
1302
+ return { kind: "needs-verification", wrapperId, childId };
1303
+ }
1304
+ if (dropsOwnStyle(wrapperComputed, childAfter ? norm.normalizeStyleMap(childAfter) : null, norm)) {
1305
+ return { kind: "needs-verification", wrapperId, childId };
1306
+ }
1307
+ if (addsParentContextStyle(childBefore, childAfter, norm) && !newParentIsFlexOrGrid(before, wrapper, norm)) {
1308
+ return { kind: "needs-verification", wrapperId, childId };
1309
+ }
1310
+ return { kind: "provably-safe", wrapperId, childId };
1311
+ }
1312
+
1313
+ // ../core/src/pass-manager.ts
1314
+ var DEFAULT_FIXPOINT = {
1315
+ maxIterations: 16,
1316
+ phases: { flatten: 16, compress: 8, extract: 4 },
1317
+ onBudgetExhausted: "warn",
1318
+ detectOscillation: true
1319
+ };
1320
+ var PHASE_ORDER = ["flatten", "compress", "extract"];
1321
+ function stampOrigin(draft, pattern) {
1194
1322
  return {
1195
1323
  ...draft,
1196
- origin: { pattern: pattern2.name, category: pattern2.category, safety: pattern2.safety }
1324
+ origin: { pattern: pattern.name, category: pattern.category, safety: pattern.safety }
1197
1325
  };
1198
1326
  }
1199
1327
  function patternsForPhase(passes, phase) {
@@ -1245,69 +1373,76 @@ function flattenWouldDropStyle(before, after, ops, resolver, normalizer2) {
1245
1373
  }
1246
1374
  return false;
1247
1375
  }
1376
+ function flattenVerdict(before, after, ops, ctx) {
1377
+ if (flattenWouldDropStyle(before, after, ops, ctx.resolver, ctx.normalizer)) return "revert";
1378
+ const gate = ctx.gate ?? "all";
1379
+ if (gate === "all") return "commit";
1380
+ const cls = classifyFlattenOps(before, after, ops, ctx.normalizer);
1381
+ return cls.kind === "provably-safe" ? "commit" : "revert";
1382
+ }
1383
+ function evaluateElement(doc, elId, patterns, ctx, factory, phase, iteration, diagnostics) {
1384
+ for (const pattern of patterns) {
1385
+ if (pattern.safety > ctx.safetyCeiling) continue;
1386
+ let drafts = [];
1387
+ try {
1388
+ const mctx = buildMatchContext(doc, elId, ctx.resolver, ctx.selectors, ctx.safetyCeiling, phase, iteration);
1389
+ const result = pattern.evaluate(mctx, factory);
1390
+ if (!result) continue;
1391
+ drafts = result.ops;
1392
+ if (result.diagnostics) for (const d3 of result.diagnostics) diagnostics.push(d3);
1393
+ } catch (err) {
1394
+ diagnostics.push({
1395
+ code: "DF_PATTERN_THREW",
1396
+ severity: "error",
1397
+ message: `pattern '${pattern.name}' threw: ${String(err?.message ?? err)}`,
1398
+ nodeId: elId,
1399
+ pattern: pattern.name,
1400
+ phase,
1401
+ iteration,
1402
+ cause: err
1403
+ });
1404
+ continue;
1405
+ }
1406
+ if (drafts.length === 0) continue;
1407
+ return { ops: drafts.map((d3) => stampOrigin(d3, pattern)), pattern };
1408
+ }
1409
+ return null;
1410
+ }
1411
+ function revertDiagnostic(pattern, elId, phase, iteration, resolverId) {
1412
+ return {
1413
+ code: "DF_VERIFY_REVERTED",
1414
+ severity: "warn",
1415
+ message: `flatten '${pattern.name}' reverted on node ${elId}: it would change rendering (drops a style, establishes layout context, or assumes a parent context) and was not proven safe by resolver '${resolverId}'`,
1416
+ nodeId: elId,
1417
+ pattern: pattern.name,
1418
+ phase,
1419
+ iteration
1420
+ };
1421
+ }
1248
1422
  function runSweep(state, patterns, ctx, factory, phase, iteration, touched, diagnostics, flattenBarred) {
1249
1423
  let appliedOps = 0;
1250
- const resolver = ctx.resolver;
1251
- const selectors = ctx.selectors;
1252
1424
  for (const elId of elementIds(state.doc)) {
1253
1425
  const el = getElement(state.doc, elId);
1254
1426
  if (!el) continue;
1255
1427
  if (phase === "flatten" && flattenBarred.has(elId)) continue;
1256
- for (const pattern2 of patterns) {
1257
- if (pattern2.safety > ctx.safetyCeiling) continue;
1258
- let drafts = [];
1259
- try {
1260
- const mctx = buildMatchContext(
1261
- state.doc,
1262
- elId,
1263
- resolver,
1264
- selectors,
1265
- ctx.safetyCeiling,
1266
- phase,
1267
- iteration
1268
- );
1269
- const result = pattern2.evaluate(mctx, factory);
1270
- if (!result) continue;
1271
- drafts = result.ops;
1272
- if (result.diagnostics) for (const d3 of result.diagnostics) diagnostics.push(d3);
1273
- } catch (err) {
1274
- diagnostics.push({
1275
- code: "DF_PATTERN_THREW",
1276
- severity: "error",
1277
- message: `pattern '${pattern2.name}' threw: ${String(err?.message ?? err)}`,
1278
- nodeId: elId,
1279
- pattern: pattern2.name,
1280
- phase,
1281
- iteration,
1282
- cause: err
1283
- });
1428
+ const evaluated = evaluateElement(state.doc, elId, patterns, ctx, factory, phase, iteration, diagnostics);
1429
+ if (!evaluated) continue;
1430
+ const { ops, pattern } = evaluated;
1431
+ const outcome = applyOps(state.doc, ops, ctx);
1432
+ for (const d3 of outcome.result.diagnostics) diagnostics.push(d3);
1433
+ if (outcome.result.appliedGroups === 0) continue;
1434
+ if (phase === "flatten") {
1435
+ const verdict = flattenVerdict(state.doc, outcome.doc, ops, ctx);
1436
+ if (verdict !== "commit") {
1437
+ diagnostics.push(revertDiagnostic(pattern, elId, phase, iteration, ctx.resolver.id));
1438
+ flattenBarred.add(elId);
1284
1439
  continue;
1285
1440
  }
1286
- if (drafts.length === 0) continue;
1287
- const ops = drafts.map((d3) => stampOrigin(d3, pattern2));
1288
- const outcome = applyOps(state.doc, ops, ctx);
1289
- for (const d3 of outcome.result.diagnostics) diagnostics.push(d3);
1290
- if (outcome.result.appliedGroups > 0) {
1291
- if (phase === "flatten" && flattenWouldDropStyle(state.doc, outcome.doc, ops, resolver, ctx.normalizer)) {
1292
- diagnostics.push({
1293
- code: "DF_VERIFY_REVERTED",
1294
- severity: "warn",
1295
- message: `flatten '${pattern2.name}' reverted on node ${elId}: residual style is not reproducible by resolver '${resolver.id}', so flattening would drop it`,
1296
- nodeId: elId,
1297
- pattern: pattern2.name,
1298
- phase,
1299
- iteration
1300
- });
1301
- flattenBarred.add(elId);
1302
- break;
1303
- }
1304
- state.doc = outcome.doc;
1305
- appliedOps += outcome.result.appliedGroups;
1306
- for (const id of outcome.result.touched) touched.add(id);
1307
- for (const id of outcome.result.created) touched.add(id);
1308
- break;
1309
- }
1310
1441
  }
1442
+ state.doc = outcome.doc;
1443
+ appliedOps += outcome.result.appliedGroups;
1444
+ for (const id of outcome.result.touched) touched.add(id);
1445
+ for (const id of outcome.result.created) touched.add(id);
1311
1446
  }
1312
1447
  return appliedOps;
1313
1448
  }
@@ -1322,6 +1457,26 @@ function docFingerprint(doc) {
1322
1457
  visit(doc.root);
1323
1458
  return parts.join("|");
1324
1459
  }
1460
+ function finalizePhase(phase, s, budget, onBudgetExhausted) {
1461
+ if (!s.converged && s.haltReason !== "oscillation") {
1462
+ s.haltReason = "budget";
1463
+ s.diagnostics.push({
1464
+ code: "DF_FIXPOINT_BUDGET",
1465
+ severity: onBudgetExhausted === "error" ? "error" : "warn",
1466
+ message: `phase '${phase}' exhausted ${budget}-iteration budget`,
1467
+ phase,
1468
+ iteration: s.iterations
1469
+ });
1470
+ }
1471
+ return {
1472
+ phase,
1473
+ iterations: s.iterations,
1474
+ converged: s.converged,
1475
+ haltReason: s.haltReason,
1476
+ touched: s.touched,
1477
+ diagnostics: s.diagnostics
1478
+ };
1479
+ }
1325
1480
  function runPasses(doc, passes, ctx, config) {
1326
1481
  const cfg = { ...DEFAULT_FIXPOINT, ...config };
1327
1482
  const factory = createRewriteFactory();
@@ -1330,76 +1485,54 @@ function runPasses(doc, passes, ctx, config) {
1330
1485
  const flattenBarred = /* @__PURE__ */ new Set();
1331
1486
  for (const phase of PHASE_ORDER) {
1332
1487
  const patterns = patternsForPhase(passes, phase);
1333
- const phaseTouched = /* @__PURE__ */ new Set();
1334
- const diagnostics = [];
1335
1488
  const budget = cfg.phases[phase] ?? cfg.maxIterations;
1336
- let iterations = 0;
1337
- let converged = false;
1338
- let haltReason = "converged";
1339
- const seen = /* @__PURE__ */ new Set();
1489
+ const s = {
1490
+ iterations: 0,
1491
+ converged: false,
1492
+ haltReason: "converged",
1493
+ touched: /* @__PURE__ */ new Set(),
1494
+ diagnostics: [],
1495
+ seen: /* @__PURE__ */ new Set()
1496
+ };
1340
1497
  if (patterns.length === 0) {
1341
- results.push({
1342
- phase,
1343
- iterations: 0,
1344
- converged: true,
1345
- haltReason: "converged",
1346
- touched: phaseTouched,
1347
- diagnostics
1348
- });
1498
+ s.converged = true;
1499
+ results.push(finalizePhase(phase, s, budget, cfg.onBudgetExhausted));
1349
1500
  continue;
1350
1501
  }
1351
- while (iterations < budget) {
1352
- iterations += 1;
1502
+ while (s.iterations < budget) {
1503
+ s.iterations += 1;
1353
1504
  const applied = runSweep(
1354
1505
  state,
1355
1506
  patterns,
1356
1507
  ctx,
1357
1508
  factory,
1358
1509
  phase,
1359
- iterations,
1360
- phaseTouched,
1361
- diagnostics,
1510
+ s.iterations,
1511
+ s.touched,
1512
+ s.diagnostics,
1362
1513
  flattenBarred
1363
1514
  );
1364
1515
  if (applied === 0) {
1365
- converged = true;
1366
- haltReason = "converged";
1516
+ s.converged = true;
1367
1517
  break;
1368
1518
  }
1369
1519
  if (cfg.detectOscillation) {
1370
1520
  const fp = docFingerprint(state.doc);
1371
- if (seen.has(fp)) {
1372
- haltReason = "oscillation";
1373
- diagnostics.push({
1521
+ if (s.seen.has(fp)) {
1522
+ s.haltReason = "oscillation";
1523
+ s.diagnostics.push({
1374
1524
  code: "DF_FIXPOINT_OSCILLATION",
1375
1525
  severity: "warn",
1376
1526
  message: `phase '${phase}' oscillated; halting`,
1377
1527
  phase,
1378
- iteration: iterations
1528
+ iteration: s.iterations
1379
1529
  });
1380
1530
  break;
1381
1531
  }
1382
- seen.add(fp);
1532
+ s.seen.add(fp);
1383
1533
  }
1384
1534
  }
1385
- if (!converged && haltReason !== "oscillation") {
1386
- haltReason = "budget";
1387
- diagnostics.push({
1388
- code: "DF_FIXPOINT_BUDGET",
1389
- severity: cfg.onBudgetExhausted === "error" ? "error" : "warn",
1390
- message: `phase '${phase}' exhausted ${budget}-iteration budget`,
1391
- phase,
1392
- iteration: iterations
1393
- });
1394
- }
1395
- results.push({
1396
- phase,
1397
- iterations,
1398
- converged,
1399
- haltReason,
1400
- touched: phaseTouched,
1401
- diagnostics
1402
- });
1535
+ results.push(finalizePhase(phase, s, budget, cfg.onBudgetExhausted));
1403
1536
  }
1404
1537
  return { doc: state.doc, results };
1405
1538
  }
@@ -1482,7 +1615,9 @@ init_cjs_shims();
1482
1615
 
1483
1616
  // ../frontend-jsx/src/frontend.ts
1484
1617
  init_cjs_shims();
1485
- var import_parser = require("@babel/parser");
1618
+
1619
+ // ../frontend-jsx/src/frontend-ast.ts
1620
+ init_cjs_shims();
1486
1621
  var import_traverse = __toESM(require("@babel/traverse"), 1);
1487
1622
  var traverse = typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
1488
1623
  var JSX_LANGS = ["jsx", "tsx"];
@@ -1546,10 +1681,71 @@ function classFormOf(node) {
1546
1681
  return "call";
1547
1682
  }
1548
1683
  }
1684
+ function findNestedJsxRoots(root) {
1685
+ const out = [];
1686
+ const seen = /* @__PURE__ */ new Set();
1687
+ const visit = (n) => {
1688
+ if (!n || seen.has(n)) return;
1689
+ seen.add(n);
1690
+ switch (n.type) {
1691
+ case "JSXElement":
1692
+ case "JSXFragment":
1693
+ out.push(n);
1694
+ return;
1695
+ case "ParenthesizedExpression":
1696
+ case "TSNonNullExpression":
1697
+ case "TSAsExpression":
1698
+ case "TSSatisfiesExpression":
1699
+ case "TSTypeAssertion":
1700
+ visit(n.expression);
1701
+ return;
1702
+ case "LogicalExpression":
1703
+ visit(n.left);
1704
+ visit(n.right);
1705
+ return;
1706
+ case "ConditionalExpression":
1707
+ visit(n.consequent);
1708
+ visit(n.alternate);
1709
+ return;
1710
+ case "SequenceExpression":
1711
+ for (const e2 of n.expressions) visit(e2);
1712
+ return;
1713
+ case "CallExpression":
1714
+ case "OptionalCallExpression":
1715
+ for (const a of n.arguments) visit(a);
1716
+ return;
1717
+ case "ArrowFunctionExpression":
1718
+ case "FunctionExpression":
1719
+ visit(n.body);
1720
+ return;
1721
+ case "BlockStatement":
1722
+ for (const s of n.body) visit(s);
1723
+ return;
1724
+ case "ReturnStatement":
1725
+ visit(n.argument);
1726
+ return;
1727
+ case "IfStatement":
1728
+ visit(n.consequent);
1729
+ visit(n.alternate);
1730
+ return;
1731
+ case "ArrayExpression":
1732
+ for (const el of n.elements) visit(el);
1733
+ return;
1734
+ default:
1735
+ return;
1736
+ }
1737
+ };
1738
+ visit(root);
1739
+ return out;
1740
+ }
1549
1741
  function looksLikeJsx(id, code) {
1550
1742
  if (/\.[jt]sx$/i.test(id)) return true;
1551
1743
  return /<\/?[A-Za-z][\w.-]*|<>/.test(code);
1552
1744
  }
1745
+
1746
+ // ../frontend-jsx/src/frontend-parse.ts
1747
+ init_cjs_shims();
1748
+ var import_parser = require("@babel/parser");
1553
1749
  function doParse(code, ctx) {
1554
1750
  const diagnostics = [];
1555
1751
  const doc = createDocument("jsx");
@@ -1651,7 +1847,8 @@ function doParse(code, ctx) {
1651
1847
  }
1652
1848
  return { kind: "dynamic", expr: internExpr(v2, false), span: spanOf(v2) ?? void 0 };
1653
1849
  };
1654
- const buildChild = (node, parentId) => {
1850
+ const buildNestedRoot = (jsx, parentId) => jsx.type === "JSXFragment" ? buildFragment(jsx, parentId) : buildElement(jsx, parentId);
1851
+ const appendChild = (node, parentId, out) => {
1655
1852
  switch (node.type) {
1656
1853
  case "JSXText": {
1657
1854
  const id = doc.alloc.next();
@@ -1663,36 +1860,47 @@ function doParse(code, ctx) {
1663
1860
  collapsible: /^\s*$/.test(node.value)
1664
1861
  })
1665
1862
  );
1666
- return id;
1863
+ out.push(id);
1864
+ return;
1667
1865
  }
1668
1866
  case "JSXExpressionContainer": {
1669
- if (node.expression.type === "JSXEmptyExpression") return null;
1867
+ const expr = node.expression;
1868
+ if (expr.type === "JSXEmptyExpression") return;
1869
+ if (expr.type === "JSXElement" || expr.type === "JSXFragment") {
1870
+ out.push(buildNestedRoot(expr, parentId));
1871
+ return;
1872
+ }
1670
1873
  const id = doc.alloc.next();
1671
- const ref = internExpr(node.expression, false);
1874
+ const ref = internExpr(expr, false);
1672
1875
  doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
1673
- return id;
1876
+ out.push(id);
1877
+ for (const jsx of findNestedJsxRoots(expr)) out.push(buildNestedRoot(jsx, parentId));
1878
+ return;
1674
1879
  }
1675
1880
  case "JSXSpreadChild": {
1676
1881
  const id = doc.alloc.next();
1677
1882
  const ref = internExpr(node.expression, true);
1678
1883
  doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
1679
- return id;
1884
+ out.push(id);
1885
+ for (const jsx of findNestedJsxRoots(node.expression)) {
1886
+ out.push(buildNestedRoot(jsx, parentId));
1887
+ }
1888
+ return;
1680
1889
  }
1681
1890
  case "JSXElement":
1682
- return buildElement(node, parentId);
1891
+ out.push(buildElement(node, parentId));
1892
+ return;
1683
1893
  case "JSXFragment":
1684
- return buildFragment(node, parentId);
1894
+ out.push(buildFragment(node, parentId));
1895
+ return;
1685
1896
  default:
1686
- return null;
1897
+ return;
1687
1898
  }
1688
1899
  };
1689
1900
  const buildFragment = (node, parentId) => {
1690
1901
  const id = doc.alloc.next();
1691
1902
  const children = [];
1692
- for (const c of node.children) {
1693
- const cid = buildChild(c, id);
1694
- if (cid != null) children.push(cid);
1695
- }
1903
+ for (const c of node.children) appendChild(c, id, children);
1696
1904
  doc.nodes.set(id, createFragment(id, { children, parent: parentId, span: spanOf(node) }));
1697
1905
  backref.set(id, {
1698
1906
  nodeId: id,
@@ -1735,10 +1943,7 @@ function doParse(code, ctx) {
1735
1943
  }
1736
1944
  const attrs = { entries, spreads, order };
1737
1945
  const children = [];
1738
- for (const c of node.children) {
1739
- const cid = buildChild(c, id);
1740
- if (cid != null) children.push(cid);
1741
- }
1946
+ for (const c of node.children) appendChild(c, id, children);
1742
1947
  for (const cid of children) {
1743
1948
  const cn = doc.nodes.get(cid);
1744
1949
  if (cn && cn.kind === "expr") {
@@ -1797,13 +2002,13 @@ function doParse(code, ctx) {
1797
2002
  };
1798
2003
  const roots = [];
1799
2004
  traverse(ast, {
1800
- JSXElement(path6) {
1801
- roots.push(path6.node);
1802
- path6.skip();
2005
+ JSXElement(path7) {
2006
+ roots.push(path7.node);
2007
+ path7.skip();
1803
2008
  },
1804
- JSXFragment(path6) {
1805
- roots.push(path6.node);
1806
- path6.skip();
2009
+ JSXFragment(path7) {
2010
+ roots.push(path7.node);
2011
+ path7.skip();
1807
2012
  }
1808
2013
  });
1809
2014
  const rootFrag = doc.nodes.get(doc.root);
@@ -1813,6 +2018,8 @@ function doParse(code, ctx) {
1813
2018
  }
1814
2019
  return { doc, diagnostics };
1815
2020
  }
2021
+
2022
+ // ../frontend-jsx/src/frontend.ts
1816
2023
  var jsxFrontend = {
1817
2024
  name: "babel-jsx",
1818
2025
  langs: JSX_LANGS,
@@ -1899,6 +2106,61 @@ function editClasses(ms, doc, sf, el) {
1899
2106
  ms.appendLeft(insertAt, ` className="${tokens.join(" ")}"`);
1900
2107
  return true;
1901
2108
  }
2109
+ function extractKeyAttr(openTag) {
2110
+ const m2 = /(^|\s)key\s*=\s*/.exec(openTag);
2111
+ if (!m2) return null;
2112
+ const keyStart = m2.index + m2[1].length;
2113
+ let i = m2.index + m2[0].length;
2114
+ const ch = openTag[i];
2115
+ if (ch === "{") {
2116
+ let depth = 0;
2117
+ for (; i < openTag.length; i += 1) {
2118
+ const c = openTag[i];
2119
+ if (c === "{") depth += 1;
2120
+ else if (c === "}") {
2121
+ depth -= 1;
2122
+ if (depth === 0) {
2123
+ i += 1;
2124
+ break;
2125
+ }
2126
+ }
2127
+ }
2128
+ } else if (ch === '"' || ch === "'") {
2129
+ const q2 = ch;
2130
+ i += 1;
2131
+ for (; i < openTag.length; i += 1) {
2132
+ if (openTag[i] === q2) {
2133
+ i += 1;
2134
+ break;
2135
+ }
2136
+ }
2137
+ } else {
2138
+ for (; i < openTag.length; i += 1) {
2139
+ if (/[\s>/]/.test(openTag[i])) break;
2140
+ }
2141
+ }
2142
+ return openTag.slice(keyStart, i);
2143
+ }
2144
+ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
2145
+ const open = region.openTagSpan;
2146
+ if (!open || open.file !== sf.id) return;
2147
+ const keyAttr = extractKeyAttr(sf.text.slice(open.start, open.end));
2148
+ if (!keyAttr) return;
2149
+ const inside = [];
2150
+ for (const n of kept) {
2151
+ if (n.kind !== "element" || !n.span || n.span.file !== sf.id) continue;
2152
+ if (strictlyContains(region.span, n.span)) inside.push(n);
2153
+ }
2154
+ const maximal = inside.filter(
2155
+ (n) => !inside.some((o2) => o2 !== n && o2.span && n.span && strictlyContains(o2.span, n.span))
2156
+ );
2157
+ if (maximal.length !== 1) return;
2158
+ const child = maximal[0];
2159
+ const childOpen = doc.backref.get(child.id)?.openTagSpan;
2160
+ if (!childOpen || childOpen.file !== sf.id) return;
2161
+ if (extractKeyAttr(sf.text.slice(childOpen.start, childOpen.end))) return;
2162
+ ms.appendLeft(childOpen.start + 1 + child.tag.length, ` ${keyAttr}`);
2163
+ }
1902
2164
  function surgicalPrint(doc) {
1903
2165
  const sf = primarySource(doc);
1904
2166
  if (!sf) return null;
@@ -1920,6 +2182,7 @@ function surgicalPrint(doc) {
1920
2182
  const coveredByFull = fullRemovals.some((f) => f !== span && strictlyContains(f, span));
1921
2183
  if (coveredByFull) continue;
1922
2184
  if (r2.unwrapped) {
2185
+ transferKeyOnUnwrap(ms, doc, sf, r2.backref, kept);
1923
2186
  const open = r2.backref.openTagSpan;
1924
2187
  const close = r2.backref.closeTagSpan;
1925
2188
  if (open && open.file === sf.id && open.end > open.start) ms.remove(open.start, open.end);
@@ -2022,40 +2285,6 @@ function createJsxBackend() {
2022
2285
  // ../pattern-kit/src/index.ts
2023
2286
  init_cjs_shims();
2024
2287
 
2025
- // ../pattern-kit/src/define.ts
2026
- init_cjs_shims();
2027
- var PHASES = /* @__PURE__ */ new Set(["flatten", "compress", "extract"]);
2028
- var SAFETY_LEVELS = /* @__PURE__ */ new Set([0, 1, 2, 3]);
2029
- function fail(name, why) {
2030
- throw new Error(`definePattern(${name || "<anonymous>"}): ${why}`);
2031
- }
2032
- function definePattern(spec) {
2033
- if (spec == null || typeof spec !== "object") {
2034
- throw new Error("definePattern: spec must be an object");
2035
- }
2036
- const name = spec.name;
2037
- if (typeof name !== "string" || name.length === 0) {
2038
- fail(String(name), "name must be a non-empty string");
2039
- }
2040
- if (typeof spec.category !== "string" || !spec.category.includes("/")) {
2041
- fail(name, `category must be a "<phase>/<slug>" string (got ${JSON.stringify(spec.category)})`);
2042
- }
2043
- const phase = spec.category.split("/", 1)[0];
2044
- if (!PHASES.has(phase)) {
2045
- fail(name, `category phase must be one of flatten|compress|extract (got "${phase}")`);
2046
- }
2047
- if (!SAFETY_LEVELS.has(spec.safety)) {
2048
- fail(name, `safety must be 0|1|2|3 (got ${JSON.stringify(spec.safety)})`);
2049
- }
2050
- if (typeof spec.evaluate !== "function") {
2051
- fail(name, "evaluate must be a function");
2052
- }
2053
- if (spec.priority !== void 0 && !Number.isFinite(spec.priority)) {
2054
- fail(name, "priority must be a finite number when provided");
2055
- }
2056
- return Object.freeze({ ...spec });
2057
- }
2058
-
2059
2288
  // ../pattern-kit/src/combinators.ts
2060
2289
  init_cjs_shims();
2061
2290
 
@@ -2402,6 +2631,11 @@ var hasRef = (node) => asElement(node)?.meta.hasRef ?? false;
2402
2631
  var hasEventHandlers = (node) => asElement(node)?.meta.hasEventHandlers ?? false;
2403
2632
  var hasDynamicChildren = (node) => asElement(node)?.meta.hasDynamicChildren ?? false;
2404
2633
  var hasDynamicClasses = (node) => asElement(node)?.classes.hasDynamic ?? false;
2634
+ var opaque = (node, ctx) => {
2635
+ const el = asElement(node);
2636
+ if (!el) return false;
2637
+ return ctx.isOpaque(el);
2638
+ };
2405
2639
  var targetedByCombinator = (node, ctx) => {
2406
2640
  const el = asElement(node);
2407
2641
  if (!el) return false;
@@ -2414,9 +2648,45 @@ init_cjs_shims();
2414
2648
 
2415
2649
  // ../pattern-kit/src/pattern.ts
2416
2650
  init_cjs_shims();
2417
- function camelToKebab(key) {
2418
- if (key.startsWith("--")) return key;
2419
- return key.replace(/[A-Z]/g, (m2) => `-${m2.toLowerCase()}`);
2651
+
2652
+ // ../pattern-kit/src/define.ts
2653
+ init_cjs_shims();
2654
+ var PHASES = /* @__PURE__ */ new Set(["flatten", "compress", "extract"]);
2655
+ var SAFETY_LEVELS = /* @__PURE__ */ new Set([0, 1, 2, 3]);
2656
+ function fail(name, why) {
2657
+ throw new Error(`definePattern(${name || "<anonymous>"}): ${why}`);
2658
+ }
2659
+ function validatePattern(spec) {
2660
+ if (spec == null || typeof spec !== "object") {
2661
+ throw new Error("definePattern: spec must be an object");
2662
+ }
2663
+ const name = spec.name;
2664
+ if (typeof name !== "string" || name.length === 0) {
2665
+ fail(String(name), "name must be a non-empty string");
2666
+ }
2667
+ if (typeof spec.category !== "string" || !spec.category.includes("/")) {
2668
+ fail(name, `category must be a "<phase>/<slug>" string (got ${JSON.stringify(spec.category)})`);
2669
+ }
2670
+ const phase = spec.category.split("/", 1)[0];
2671
+ if (!PHASES.has(phase)) {
2672
+ fail(name, `category phase must be one of flatten|compress|extract (got "${phase}")`);
2673
+ }
2674
+ if (!SAFETY_LEVELS.has(spec.safety)) {
2675
+ fail(name, `safety must be 0|1|2|3 (got ${JSON.stringify(spec.safety)})`);
2676
+ }
2677
+ if (typeof spec.evaluate !== "function") {
2678
+ fail(name, "evaluate must be a function");
2679
+ }
2680
+ if (spec.priority !== void 0 && !Number.isFinite(spec.priority)) {
2681
+ fail(name, "priority must be a finite number when provided");
2682
+ }
2683
+ return Object.freeze({ ...spec });
2684
+ }
2685
+
2686
+ // ../pattern-kit/src/pattern.ts
2687
+ function camelToKebab(key) {
2688
+ if (key.startsWith("--")) return key;
2689
+ return key.replace(/[A-Z]/g, (m2) => `-${m2.toLowerCase()}`);
2420
2690
  }
2421
2691
  function plainToStyleMap(style) {
2422
2692
  const decls = /* @__PURE__ */ new Map();
@@ -2450,8 +2720,20 @@ var FLATTEN_GUARDS = and(
2450
2720
  not(targetedByCombinator),
2451
2721
  not(affectsSelectorMatching)
2452
2722
  );
2453
- function isFlattenCategory(category) {
2454
- return category.split("/", 1)[0] === "flatten";
2723
+ var COMPRESS_GUARDS = and(
2724
+ not(hasDynamicClasses),
2725
+ not(opaque),
2726
+ not(targetedByCombinator)
2727
+ );
2728
+ function autoGuardsFor(category) {
2729
+ switch (category.split("/", 1)[0]) {
2730
+ case "flatten":
2731
+ return FLATTEN_GUARDS;
2732
+ case "compress":
2733
+ return COMPRESS_GUARDS;
2734
+ default:
2735
+ return null;
2736
+ }
2455
2737
  }
2456
2738
  function compileDeclarativeMatch(m2) {
2457
2739
  const parts = [isElement(m2.tag)];
@@ -2467,7 +2749,8 @@ function compileDeclarativeMatch(m2) {
2467
2749
  function compileMatch(match, category) {
2468
2750
  if (typeof match === "function") return match;
2469
2751
  const declarative = compileDeclarativeMatch(match ?? {});
2470
- const guarded = isFlattenCategory(category) ? and(declarative, FLATTEN_GUARDS) : declarative;
2752
+ const guards = autoGuardsFor(category);
2753
+ const guarded = guards ? and(declarative, guards) : declarative;
2471
2754
  return (node, ctx) => guarded(node, ctx);
2472
2755
  }
2473
2756
  function pruneShadowed(sm, drop) {
@@ -2524,7 +2807,7 @@ function compileRewrite(rewrite) {
2524
2807
  const onConflict = rewrite.onConflict ?? "abort";
2525
2808
  return (ctx, rw) => [rw.mergeStyle(ctx.node, null, style, onConflict)];
2526
2809
  }
2527
- function pattern(config) {
2810
+ function definePattern(config) {
2528
2811
  const matchFn = compileMatch(config.match, config.category);
2529
2812
  const rewriteFn = compileRewrite(config.rewrite);
2530
2813
  const spec = {
@@ -2534,7 +2817,7 @@ function pattern(config) {
2534
2817
  priority: config.priority,
2535
2818
  precondition: config.precondition,
2536
2819
  doc: config.doc,
2537
- examples: config.examples,
2820
+ test: config.test,
2538
2821
  evaluate(ctx, rw) {
2539
2822
  if (!matchFn(ctx.node, ctx)) return null;
2540
2823
  const ops = rewriteFn(ctx, rw);
@@ -2542,7 +2825,7 @@ function pattern(config) {
2542
2825
  return { ops };
2543
2826
  }
2544
2827
  };
2545
- return definePattern(spec);
2828
+ return validatePattern(spec);
2546
2829
  }
2547
2830
 
2548
2831
  // ../patterns/src/index.ts
@@ -2551,38 +2834,103 @@ init_cjs_shims();
2551
2834
  // ../patterns/src/_registry.generated.ts
2552
2835
  init_cjs_shims();
2553
2836
 
2554
- // ../patterns/src/flatten/empty-style-div.pattern.ts
2837
+ // ../patterns/src/library/flatten/display-contents-wrapper.pattern.ts
2555
2838
  init_cjs_shims();
2556
2839
  function asEl(node) {
2557
2840
  const n = node;
2558
2841
  return n.kind === "element" ? n : null;
2559
2842
  }
2843
+ function metaOf(node) {
2844
+ return asEl(node)?.meta ?? null;
2845
+ }
2846
+ var declaresCustomProperties = (node) => metaOf(node)?.declaresCustomProperties ?? false;
2847
+ var hasSpreadAttrs = (node) => metaOf(node)?.hasSpreadAttrs ?? false;
2848
+ var isComponentNode = (node) => metaOf(node)?.isComponent ?? false;
2849
+ var hasOwnAttrs = (node) => {
2850
+ const el = asEl(node);
2851
+ if (!el) return false;
2852
+ return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
2853
+ };
2854
+ var targetedByStructuralPseudo = (node, ctx) => {
2855
+ const el = asEl(node);
2856
+ if (!el) return false;
2857
+ if (el.meta.targetedByStructuralPseudo) return true;
2858
+ return ctx.selectors.targetedByStructuralPseudo(el.id);
2859
+ };
2860
+ var displayContentsWrapper = definePattern({
2861
+ name: "display-contents-wrapper",
2862
+ category: "flatten/display-contents-wrapper",
2863
+ safety: 2,
2864
+ doc: {
2865
+ title: "Flatten display:contents wrapper",
2866
+ summary: "A div with display:contents (which generates no box) wrapping a single element child, with no own visual style, no attributes beyond an inert class, and no opacity barriers, is removed; its sole child is hoisted in its place.",
2867
+ before: '<div style="display:contents"><Child/></div>',
2868
+ after: "<Child/>",
2869
+ safetyRationale: "A display:contents element generates no box at all, so its children already render as direct children of its parent; removing it is layout-identical. It paints nothing, establishes no formatting/stacking/box context, is no containing block, carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs / custom-property coupling, and is not a combinator/structural-pseudo subject; inheritable styles are folded onto the child before removal."
2870
+ },
2871
+ match: {
2872
+ tag: "div",
2873
+ style: { display: "contents" },
2874
+ onlyChild: "element",
2875
+ paintsNothing: true,
2876
+ where: [
2877
+ not(declaresCustomProperties),
2878
+ not(hasOwnAttrs),
2879
+ not(hasDynamicClasses),
2880
+ not(hasSpreadAttrs),
2881
+ not(isComponentNode),
2882
+ not(targetedByStructuralPseudo)
2883
+ ]
2884
+ },
2885
+ rewrite: { flattenInto: "child" },
2886
+ test: {
2887
+ cases: [
2888
+ {
2889
+ // `display:contents` generates no box, so removing it is provably layout-identical → the
2890
+ // wrapper is flattened even under the conservative gate; the child is hoisted.
2891
+ before: '<div className="contents"><a className="text-blue-500">Link</a></div>',
2892
+ after: '<a className="text-blue-500">Link</a>'
2893
+ }
2894
+ ],
2895
+ noMatch: [
2896
+ // A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
2897
+ '<div className="contents" ref={rootRef}><a className="text-blue-500">Link</a></div>'
2898
+ ]
2899
+ }
2900
+ });
2901
+
2902
+ // ../patterns/src/library/flatten/empty-style-div.pattern.ts
2903
+ init_cjs_shims();
2904
+ function asEl2(node) {
2905
+ const n = node;
2906
+ return n.kind === "element" ? n : null;
2907
+ }
2560
2908
  function metaFlag2(flag) {
2561
- return (node) => Boolean(asEl(node)?.meta[flag]);
2909
+ return (node) => Boolean(asEl2(node)?.meta[flag]);
2562
2910
  }
2563
2911
  var establishesBox = metaFlag2("establishesBox");
2564
2912
  var establishesFormattingContext = metaFlag2("establishesFormattingContext");
2565
2913
  var establishesStackingContext = metaFlag2("establishesStackingContext");
2566
2914
  var isContainingBlock = metaFlag2("isContainingBlock");
2567
- var declaresCustomProperties = metaFlag2("declaresCustomProperties");
2568
- var targetedByStructuralPseudo = (node, ctx) => {
2569
- const el = asEl(node);
2915
+ var declaresCustomProperties2 = metaFlag2("declaresCustomProperties");
2916
+ var targetedByStructuralPseudo2 = (node, ctx) => {
2917
+ const el = asEl2(node);
2570
2918
  if (!el) return false;
2571
2919
  if (el.meta.targetedByStructuralPseudo) return true;
2572
2920
  return ctx.selectors.targetedByStructuralPseudo(el.id);
2573
2921
  };
2574
- var DISPLAY = "display";
2922
+ var DISPLAY2 = "display";
2575
2923
  var hasNonBlockDisplay = (node, ctx) => {
2576
- const el = asEl(node);
2924
+ const el = asEl2(node);
2577
2925
  if (!el) return false;
2578
2926
  const sm = ctx.computedOf(el) ?? el.computed;
2579
2927
  for (const block of sm.blocks.values()) {
2580
- const decl = block.decls.get(DISPLAY);
2928
+ const decl = block.decls.get(DISPLAY2);
2581
2929
  if (decl && String(decl.value) !== "block") return true;
2582
2930
  }
2583
2931
  return false;
2584
2932
  };
2585
- var emptyStyleDiv = pattern({
2933
+ var emptyStyleDiv = definePattern({
2586
2934
  name: "empty-style-div",
2587
2935
  category: "flatten/empty-style-div",
2588
2936
  safety: 1,
@@ -2603,26 +2951,29 @@ var emptyStyleDiv = pattern({
2603
2951
  not(establishesFormattingContext),
2604
2952
  not(establishesStackingContext),
2605
2953
  not(isContainingBlock),
2606
- not(declaresCustomProperties),
2607
- not(targetedByStructuralPseudo)
2954
+ not(declaresCustomProperties2),
2955
+ not(targetedByStructuralPseudo2)
2608
2956
  ]
2609
2957
  },
2610
2958
  rewrite: { flattenInto: "child" },
2611
- examples: [
2612
- {
2613
- before: '<div><span className="bg-red-200">Hi</span></div>',
2614
- after: '<span className="bg-red-200">Hi</span>'
2615
- },
2616
- {
2959
+ test: {
2960
+ cases: [
2961
+ {
2962
+ // A layout-neutral, style-free block div is a provably-safe flatten → removed, child hoisted.
2963
+ before: '<div><span className="bg-red-200">Hi</span></div>',
2964
+ after: '<span className="bg-red-200">Hi</span>'
2965
+ }
2966
+ ],
2967
+ noMatch: [
2617
2968
  // The wrapper paints its own background (own visual style) → not layout-neutral, kept.
2618
- noMatch: '<div className="bg-blue-500"><span className="bg-red-200">Hi</span></div>'
2619
- }
2620
- ]
2969
+ '<div className="bg-blue-500"><span className="bg-red-200">Hi</span></div>'
2970
+ ]
2971
+ }
2621
2972
  });
2622
2973
 
2623
- // ../patterns/src/flatten/flex-center-wrapper.pattern.ts
2974
+ // ../patterns/src/library/flatten/flex-center-wrapper.pattern.ts
2624
2975
  init_cjs_shims();
2625
- var flexCenterWrapper = pattern({
2976
+ var flexCenterWrapper = definePattern({
2626
2977
  name: "flex-center-wrapper",
2627
2978
  category: "flatten/flex-center-wrapper",
2628
2979
  safety: 2,
@@ -2643,21 +2994,65 @@ var flexCenterWrapper = pattern({
2643
2994
  flattenInto: "child",
2644
2995
  childGains: { placeSelf: "center" }
2645
2996
  },
2646
- examples: [
2647
- {
2648
- // The wrapper is removed; the surviving child gains `place-self-center` (reverse-emitted
2649
- // from the folded computed style by the resolver).
2650
- before: '<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
2651
- after: '<div className="bg-red-200 place-self-center">Hello</div>'
2652
- },
2653
- {
2654
- // onClick is a hard opacity barrier the wrapper is load-bearing, no flatten.
2655
- noMatch: '<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
2656
- }
2657
- ]
2997
+ // Collapsing a flex-centering wrapper to `place-self:center` on the child only stays centered when
2998
+ // the child's NEW parent is flex/grid; moreover the wrapper's own `display:flex` establishes a
2999
+ // formatting context. Both make this a `needs-verification` flatten, which the conservative
3000
+ // production gate (`'provably-safe'`, used by the harness) intentionally REVERTS — so every case
3001
+ // here is a no-match: the wrapper is preserved. Op-level rewrite correctness (purity, id-preserving
3002
+ // unwrap, opacity-barrier safety) is still asserted by the invariant suite over every pattern.
3003
+ test: {
3004
+ noMatch: [
3005
+ // Even under a static flex/grid parent the centering flatten is not provably layout-neutral
3006
+ // (the wrapper itself establishes a flex formatting context) left unchanged.
3007
+ '<div className="grid"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
3008
+ // Non-flex/grid parent (document root): place-self centering would not hold → left unchanged.
3009
+ '<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
3010
+ // onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
3011
+ '<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
3012
+ ]
3013
+ }
3014
+ });
3015
+
3016
+ // ../patterns/src/library/flatten/inline-flex-center-wrapper.pattern.ts
3017
+ init_cjs_shims();
3018
+ var inlineFlexCenterWrapper = definePattern({
3019
+ name: "inline-flex-center-wrapper",
3020
+ category: "flatten/inline-flex-center-wrapper",
3021
+ safety: 2,
3022
+ doc: {
3023
+ title: "Flatten inline-flex-centering wrapper",
3024
+ summary: "A div that only centers a single child (display:inline-flex; align-items:center; justify-content:center) is removed; the child gains place-self:center.",
3025
+ before: '<div style="display:inline-flex;align-items:center;justify-content:center"><Child/></div>',
3026
+ after: '<Child style="place-self:center"/>',
3027
+ safetyRationale: "Wrapper paints nothing, carries no ref/handlers/dynamic children, and is not a combinator subject; inheritable styles are folded onto the child before removal."
3028
+ },
3029
+ match: {
3030
+ tag: "div",
3031
+ style: { display: "inline-flex", alignItems: "center", justifyContent: "center" },
3032
+ onlyChild: "element",
3033
+ paintsNothing: true
3034
+ },
3035
+ rewrite: {
3036
+ flattenInto: "child",
3037
+ childGains: { placeSelf: "center" }
3038
+ },
3039
+ // Like its block-level sibling, this centering flatten is `needs-verification` (the wrapper's own
3040
+ // `display:inline-flex` establishes a formatting context, and place-self centering only holds under
3041
+ // a flex/grid parent), so the conservative production gate (`'provably-safe'`) REVERTS it — every
3042
+ // case here is a no-match. Op-level correctness is covered by the invariant suite.
3043
+ test: {
3044
+ noMatch: [
3045
+ // Even under a static flex/grid parent the centering flatten is not provably layout-neutral.
3046
+ '<div className="grid"><div className="inline-flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
3047
+ // Non-flex/grid parent (document root) → left unchanged.
3048
+ '<div className="inline-flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
3049
+ // onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
3050
+ '<div className="inline-flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
3051
+ ]
3052
+ }
2658
3053
  });
2659
3054
 
2660
- // ../patterns/src/flatten/nested-flex-merge.pattern.ts
3055
+ // ../patterns/src/library/flatten/nested-flex-merge.pattern.ts
2661
3056
  init_cjs_shims();
2662
3057
  function baseConditionStyleMap(decls) {
2663
3058
  const map = /* @__PURE__ */ new Map();
@@ -2724,7 +3119,7 @@ var isInnerFlex = and(
2724
3119
  computed(DISPLAY_FLEX),
2725
3120
  not(targetedByCombinator)
2726
3121
  );
2727
- var nestedFlexMerge = pattern({
3122
+ var nestedFlexMerge = definePattern({
2728
3123
  name: "nested-flex-merge",
2729
3124
  category: "flatten/nested-flex-merge",
2730
3125
  safety: 2,
@@ -2760,24 +3155,148 @@ var nestedFlexMerge = pattern({
2760
3155
  rw.unwrap(outer)
2761
3156
  ];
2762
3157
  },
2763
- examples: [
2764
- {
2765
- // The wrapper's flex declarations (align-items / gap) merge onto the inner flex container,
2766
- // then the wrapper is removed (its own `data-x` here just blocks the more aggressive
2767
- // passthrough-wrapper so this merge is the one that fires).
2768
- before: '<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
2769
- after: '<div className="flex flex-col gap-2 items-center">X</div>'
2770
- },
2771
- {
2772
- // A non-flex wrapper does not match the flex-container signature → left unchanged.
2773
- noMatch: '<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
3158
+ // Merging the outer flex container into the inner removes the outer's box, but a `display:flex`
3159
+ // wrapper establishes a formatting context, so this is a `needs-verification` flatten that the
3160
+ // conservative production gate (`'provably-safe'`) REVERTS every case here is a no-match. The
3161
+ // merge's op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is asserted
3162
+ // by the invariant suite over every pattern.
3163
+ test: {
3164
+ noMatch: [
3165
+ // The merge is real but not provably layout-neutral (the wrapper establishes a flex context),
3166
+ // so under the conservative gate the nested containers are left in place.
3167
+ '<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
3168
+ // A non-flex wrapper does not match the flex-container signature → left unchanged anyway.
3169
+ '<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
3170
+ ]
3171
+ }
3172
+ });
3173
+
3174
+ // ../patterns/src/library/flatten/nested-grid-merge.pattern.ts
3175
+ init_cjs_shims();
3176
+ function baseConditionStyleMap2(decls) {
3177
+ const map = /* @__PURE__ */ new Map();
3178
+ for (const [prop, value] of decls) {
3179
+ for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
3180
+ map.set(decl.property, decl);
2774
3181
  }
2775
- ]
3182
+ }
3183
+ const block = { condition: BASE_CONDITION, decls: map };
3184
+ const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
3185
+ return { blocks };
3186
+ }
3187
+ var DISPLAY_GRID = baseConditionStyleMap2([["display", "grid"]]);
3188
+ var GRID_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
3189
+ "display",
3190
+ "grid-template-columns",
3191
+ "grid-template-rows",
3192
+ "grid-template-areas",
3193
+ "grid-auto-columns",
3194
+ "grid-auto-rows",
3195
+ "grid-auto-flow",
3196
+ "justify-content",
3197
+ "align-content",
3198
+ "place-content",
3199
+ "justify-items",
3200
+ "align-items",
3201
+ "place-items",
3202
+ "row-gap",
3203
+ "column-gap"
3204
+ ]);
3205
+ function outerMergeSafe2(sm) {
3206
+ const norm = normalizer.normalizeStyleMap(sm);
3207
+ for (const block of norm.blocks.values()) {
3208
+ for (const decl of block.decls.values()) {
3209
+ if (GRID_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
3210
+ if (decl.inherited) continue;
3211
+ return false;
3212
+ }
3213
+ }
3214
+ return true;
3215
+ }
3216
+ function gridConflict(outer, inner) {
3217
+ const a = normalizer.normalizeStyleMap(outer);
3218
+ const b3 = normalizer.normalizeStyleMap(inner);
3219
+ for (const [key, blockA] of a.blocks) {
3220
+ const blockB = b3.blocks.get(key);
3221
+ if (!blockB) continue;
3222
+ for (const [prop, declA] of blockA.decls) {
3223
+ if (!GRID_CONTAINER_PROPERTIES.has(String(prop))) continue;
3224
+ const declB = blockB.decls.get(prop);
3225
+ if (declB && declB.value !== declA.value) return true;
3226
+ }
3227
+ }
3228
+ return false;
3229
+ }
3230
+ function extractGridStyle(sm) {
3231
+ const blocks = /* @__PURE__ */ new Map();
3232
+ for (const [key, block] of sm.blocks) {
3233
+ const decls = /* @__PURE__ */ new Map();
3234
+ for (const [prop, decl] of block.decls) {
3235
+ if (GRID_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
3236
+ }
3237
+ if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
3238
+ }
3239
+ return { blocks };
3240
+ }
3241
+ var isInnerGrid = and(
3242
+ isElement("div"),
3243
+ computed(DISPLAY_GRID),
3244
+ not(targetedByCombinator)
3245
+ );
3246
+ var nestedGridMerge = definePattern({
3247
+ name: "nested-grid-merge",
3248
+ category: "flatten/nested-grid-merge",
3249
+ safety: 2,
3250
+ doc: {
3251
+ title: "Merge nested grid containers",
3252
+ summary: "A grid container whose only child is itself a grid container with non-conflicting grid properties is collapsed into one; the wrapper is removed and its grid declarations merge onto the surviving child.",
3253
+ before: '<div style="display:grid;gap:8px"><div style="display:grid;grid-template-columns:1fr 1fr"/></div>',
3254
+ after: '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"/>',
3255
+ safetyRationale: "The wrapper paints nothing, declares only grid-container/inheritable properties, carries no ref/handlers/dynamic children, and is not a combinator subject; the two containers do not conflict on any grid property, so the union is unambiguous and lossless."
3256
+ },
3257
+ match: {
3258
+ tag: "div",
3259
+ style: { display: "grid" },
3260
+ onlyChild: "element",
3261
+ paintsNothing: true
3262
+ },
3263
+ rewrite: (ctx, rw) => {
3264
+ const outer = ctx.node;
3265
+ const inner = ctx.onlyElementChild();
3266
+ if (!inner) return null;
3267
+ if (!isInnerGrid(inner, ctx)) return null;
3268
+ const outerStyle = ctx.computed();
3269
+ const innerStyle = ctx.computedOf(inner);
3270
+ if (!outerMergeSafe2(outerStyle)) return null;
3271
+ if (gridConflict(outerStyle, innerStyle)) return null;
3272
+ return [
3273
+ // 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
3274
+ rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
3275
+ // 2. Transfer the wrapper's grid-container declarations onto the child (target-wins keeps the
3276
+ // child's value for any shared property — identical anyway, we proved non-conflict).
3277
+ rw.mergeStyle(inner, null, extractGridStyle(outerStyle), "target-wins"),
3278
+ // 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
3279
+ rw.unwrap(outer)
3280
+ ];
3281
+ },
3282
+ // Like its flex sibling, this merge removes the outer container's box, but a `display:grid` wrapper
3283
+ // establishes a formatting context, so it is a `needs-verification` flatten that the conservative
3284
+ // production gate (`'provably-safe'`) REVERTS — every case here is a no-match. Op-level correctness
3285
+ // is asserted by the invariant suite over every pattern.
3286
+ test: {
3287
+ noMatch: [
3288
+ // The merge is real but not provably layout-neutral (the wrapper establishes a grid context),
3289
+ // so under the conservative gate the nested containers are left in place.
3290
+ '<div className="grid gap-2" data-x="1"><div className="grid grid-cols-2">X</div></div>',
3291
+ // A non-grid wrapper does not match the grid-container signature → left unchanged anyway.
3292
+ '<div className="block bg-blue-500"><div className="grid grid-cols-2">X</div></div>'
3293
+ ]
3294
+ }
2776
3295
  });
2777
3296
 
2778
- // ../patterns/src/flatten/passthrough-wrapper.pattern.ts
3297
+ // ../patterns/src/library/flatten/passthrough-wrapper.pattern.ts
2779
3298
  init_cjs_shims();
2780
- function metaOf(node) {
3299
+ function metaOf2(node) {
2781
3300
  const n = node;
2782
3301
  return n.kind === "element" ? n.meta : null;
2783
3302
  }
@@ -2786,24 +3305,24 @@ function elementOf(node) {
2786
3305
  return n.kind === "element" ? n : null;
2787
3306
  }
2788
3307
  var establishesContext = (node) => {
2789
- const m2 = metaOf(node);
3308
+ const m2 = metaOf2(node);
2790
3309
  if (!m2) return false;
2791
3310
  return m2.establishesBox || m2.establishesFormattingContext || m2.establishesStackingContext || m2.isContainingBlock || m2.declaresCustomProperties;
2792
3311
  };
2793
- var hasSpreadAttrs = (node) => metaOf(node)?.hasSpreadAttrs ?? false;
2794
- var isComponentNode = (node) => metaOf(node)?.isComponent ?? false;
2795
- var hasOwnAttrs = (node) => {
3312
+ var hasSpreadAttrs2 = (node) => metaOf2(node)?.hasSpreadAttrs ?? false;
3313
+ var isComponentNode2 = (node) => metaOf2(node)?.isComponent ?? false;
3314
+ var hasOwnAttrs2 = (node) => {
2796
3315
  const el = elementOf(node);
2797
3316
  if (!el) return false;
2798
3317
  return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
2799
3318
  };
2800
- var targetedByStructuralPseudo2 = (node, ctx) => {
3319
+ var targetedByStructuralPseudo3 = (node, ctx) => {
2801
3320
  const el = elementOf(node);
2802
3321
  if (!el) return false;
2803
3322
  if (el.meta.targetedByStructuralPseudo) return true;
2804
3323
  return ctx.selectors.targetedByStructuralPseudo(el.id);
2805
3324
  };
2806
- var passthroughWrapper = pattern({
3325
+ var passthroughWrapper = definePattern({
2807
3326
  name: "passthrough-wrapper",
2808
3327
  category: "flatten/passthrough-wrapper",
2809
3328
  safety: 2,
@@ -2820,27 +3339,34 @@ var passthroughWrapper = pattern({
2820
3339
  paintsNothing: true,
2821
3340
  where: [
2822
3341
  not(establishesContext),
2823
- not(hasOwnAttrs),
3342
+ not(hasOwnAttrs2),
2824
3343
  not(hasDynamicClasses),
2825
- not(hasSpreadAttrs),
2826
- not(isComponentNode),
2827
- not(targetedByStructuralPseudo2)
3344
+ not(hasSpreadAttrs2),
3345
+ not(isComponentNode2),
3346
+ not(targetedByStructuralPseudo3)
2828
3347
  ]
2829
3348
  },
2830
3349
  rewrite: { flattenInto: "child" },
2831
- examples: [
2832
- {
2833
- before: '<div className="flex"><a className="bg-red-200">Link</a></div>',
2834
- after: '<a className="bg-red-200">Link</a>'
2835
- },
2836
- {
3350
+ test: {
3351
+ cases: [
3352
+ {
3353
+ // A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
3354
+ // flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
3355
+ before: '<div><a className="bg-red-200">Link</a></div>',
3356
+ after: '<a className="bg-red-200">Link</a>'
3357
+ }
3358
+ ],
3359
+ noMatch: [
2837
3360
  // A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
2838
- noMatch: '<div ref={rootRef}><a className="bg-red-200">Link</a></div>'
2839
- }
2840
- ]
3361
+ '<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
3362
+ // A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
3363
+ // provably layout-neutral → the conservative gate leaves it in place.
3364
+ '<div className="flex"><a className="bg-red-200">Link</a></div>'
3365
+ ]
3366
+ }
2841
3367
  });
2842
3368
 
2843
- // ../patterns/src/flatten/redundant-fragment.pattern.ts
3369
+ // ../patterns/src/library/flatten/redundant-fragment.pattern.ts
2844
3370
  init_cjs_shims();
2845
3371
  function parentIsRedundantFragment(node, ctx) {
2846
3372
  const el = node;
@@ -2863,7 +3389,7 @@ function parentIsRedundantFragment(node, ctx) {
2863
3389
  if (ctx.selectors.reparentImpact(fid).size > 0) return false;
2864
3390
  return true;
2865
3391
  }
2866
- var redundantFragment = pattern({
3392
+ var redundantFragment = definePattern({
2867
3393
  name: "redundant-fragment",
2868
3394
  category: "flatten/redundant-fragment",
2869
3395
  safety: 1,
@@ -2882,61 +3408,314 @@ var redundantFragment = pattern({
2882
3408
  if (!fragment || fragment.kind !== "fragment") return null;
2883
3409
  return [rw.unwrap(fragment)];
2884
3410
  },
2885
- examples: [
2886
- {
2887
- before: '<><span className="bg-red-200">Hi</span></>',
2888
- after: '<span className="bg-red-200">Hi</span>'
2889
- },
2890
- {
3411
+ test: {
3412
+ cases: [
3413
+ {
3414
+ // A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
3415
+ // → a provably-safe flatten: the child is spliced up into the fragment's slot.
3416
+ before: '<><span className="bg-red-200">Hi</span></>',
3417
+ after: '<span className="bg-red-200">Hi</span>'
3418
+ }
3419
+ ],
3420
+ noMatch: [
2891
3421
  // Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
2892
- noMatch: '<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
2893
- }
2894
- ]
3422
+ '<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
3423
+ ]
3424
+ }
2895
3425
  });
2896
3426
 
2897
- // ../patterns/src/compress/dedupe-classes.pattern.ts
3427
+ // ../patterns/src/library/flatten/redundant-inline-wrapper.pattern.ts
2898
3428
  init_cjs_shims();
2899
- function elementOf2(node) {
3429
+ function asEl3(node) {
2900
3430
  const n = node;
2901
3431
  return n.kind === "element" ? n : null;
2902
3432
  }
2903
- var hasDangerousHtml = (node) => elementOf2(node)?.meta.hasDangerousHtml ?? false;
2904
- var isOpaque = (node, ctx) => ctx.isOpaque(node);
2905
- function findRedundantClasses(computed2) {
2906
- const winners = /* @__PURE__ */ new Set();
2907
- const shadowed = /* @__PURE__ */ new Set();
2908
- for (const block of computed2.blocks.values()) {
2909
- for (const decl of block.decls.values()) {
2910
- if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
2911
- for (const o2 of decl.shadowed ?? []) {
2912
- if (o2.kind === "class") shadowed.add(o2.className);
2913
- }
2914
- }
2915
- }
2916
- return { winners, shadowed };
3433
+ function metaOf3(node) {
3434
+ return asEl3(node)?.meta ?? null;
2917
3435
  }
2918
- var dedupeClasses = pattern({
2919
- name: "dedupe-classes",
2920
- category: "compress/dedupe-classes",
2921
- safety: 1,
3436
+ var establishesContext2 = (node) => {
3437
+ const m2 = metaOf3(node);
3438
+ if (!m2) return false;
3439
+ return m2.establishesBox || m2.establishesFormattingContext || m2.establishesStackingContext || m2.isContainingBlock || m2.declaresCustomProperties;
3440
+ };
3441
+ var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
3442
+ var isComponentNode3 = (node) => metaOf3(node)?.isComponent ?? false;
3443
+ var hasOwnAttrs3 = (node) => {
3444
+ const el = asEl3(node);
3445
+ if (!el) return false;
3446
+ return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
3447
+ };
3448
+ var targetedByStructuralPseudo4 = (node, ctx) => {
3449
+ const el = asEl3(node);
3450
+ if (!el) return false;
3451
+ if (el.meta.targetedByStructuralPseudo) return true;
3452
+ return ctx.selectors.targetedByStructuralPseudo(el.id);
3453
+ };
3454
+ var DISPLAY3 = "display";
3455
+ var hasNonInlineDisplay = (node, ctx) => {
3456
+ const el = asEl3(node);
3457
+ if (!el) return false;
3458
+ const sm = ctx.computedOf(el) ?? el.computed;
3459
+ for (const block of sm.blocks.values()) {
3460
+ const decl = block.decls.get(DISPLAY3);
3461
+ if (decl && String(decl.value) !== "inline") return true;
3462
+ }
3463
+ return false;
3464
+ };
3465
+ var redundantInlineWrapper = definePattern({
3466
+ name: "redundant-inline-wrapper",
3467
+ category: "flatten/redundant-inline-wrapper",
3468
+ safety: 2,
2922
3469
  doc: {
2923
- title: "Dedupe fully-overridden class tokens",
2924
- summary: "Drops class tokens whose every declaration is overridden by a later token resolving to the same property; the surviving token set produces a byte-for-byte identical computed style.",
2925
- before: '<p class="text-sm text-lg" />',
2926
- after: '<p class="text-lg" />',
2927
- safetyRationale: "A fully-overridden token contributes nothing to the computed style in any condition, so removing it changes no pixels. Dynamic/opaque class lists, ref/handler/dynamic-children/raw-html barriers, combinator subjects, and selector-bound (non-droppable) tokens are excluded."
3470
+ title: "Flatten redundant inline wrapper",
3471
+ summary: "An inline span with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
3472
+ before: "<span><Child/></span>",
3473
+ after: "<Child/>",
3474
+ safetyRationale: "An empty inline box paints nothing and establishes no layout/paint/var context; with the inline default display and a single element child its removal changes no paint and no flow. The span carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject; inheritable styles are folded onto the child before removal."
2928
3475
  },
2929
3476
  match: {
3477
+ tag: "span",
3478
+ onlyChild: "element",
3479
+ paintsNothing: true,
2930
3480
  where: [
2931
- not(hasRef),
2932
- not(hasEventHandlers),
2933
- not(hasDynamicChildren),
2934
- not(hasDangerousHtml),
3481
+ not(hasNonInlineDisplay),
3482
+ not(establishesContext2),
3483
+ not(hasOwnAttrs3),
2935
3484
  not(hasDynamicClasses),
2936
- not(isOpaque),
2937
- not(targetedByCombinator)
3485
+ not(hasSpreadAttrs3),
3486
+ not(isComponentNode3),
3487
+ not(targetedByStructuralPseudo4)
2938
3488
  ]
2939
3489
  },
3490
+ rewrite: { flattenInto: "child" },
3491
+ test: {
3492
+ cases: [
3493
+ {
3494
+ // An empty inline span paints nothing and establishes no context → a provably-safe flatten:
3495
+ // the span is removed and its sole child hoisted in place.
3496
+ before: '<span><a className="text-blue-500">Link</a></span>',
3497
+ after: '<a className="text-blue-500">Link</a>'
3498
+ }
3499
+ ],
3500
+ noMatch: [
3501
+ // A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
3502
+ '<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
3503
+ // The span paints its own background (own visual style) → kept.
3504
+ '<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
3505
+ // Non-inline display (inline-block) participates in layout differently → kept.
3506
+ '<span className="inline-block"><a className="text-blue-500">Link</a></span>'
3507
+ ]
3508
+ }
3509
+ });
3510
+
3511
+ // ../patterns/src/library/compress/border-radius-shorthand.pattern.ts
3512
+ init_cjs_shims();
3513
+ var CORNERS = [
3514
+ "border-top-left-radius",
3515
+ "border-top-right-radius",
3516
+ "border-bottom-right-radius",
3517
+ "border-bottom-left-radius"
3518
+ ];
3519
+ var CORNER_SET = new Set(CORNERS);
3520
+ var BASE_KEY = conditionKey(BASE_CONDITION);
3521
+ var RADIUS = "border-radius";
3522
+ var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set([
3523
+ "initial",
3524
+ "inherit",
3525
+ "unset",
3526
+ "revert",
3527
+ "revert-layer"
3528
+ ]);
3529
+ function analyzeRadius(sm) {
3530
+ const block = sm.blocks.get(BASE_KEY);
3531
+ if (!block) return null;
3532
+ const corners = [];
3533
+ for (const corner of CORNERS) {
3534
+ const decl = block.decls.get(corner);
3535
+ if (!decl) return null;
3536
+ corners.push(decl);
3537
+ }
3538
+ const important = corners[0].important;
3539
+ if (!corners.every((d3) => d3.important === important)) return null;
3540
+ const value = String(corners[0].value);
3541
+ if (NON_COLLAPSIBLE_VALUES.has(value)) return null;
3542
+ if (!corners.every((d3) => String(d3.value) === value)) return null;
3543
+ const relative4 = corners.some((d3) => d3.relativeToParent);
3544
+ return { value, important, relative: relative4 };
3545
+ }
3546
+ function withFoldedRadius(sm, fold) {
3547
+ const blocks = /* @__PURE__ */ new Map();
3548
+ for (const [key, block] of sm.blocks) {
3549
+ if (key !== BASE_KEY) {
3550
+ blocks.set(key, block);
3551
+ continue;
3552
+ }
3553
+ const decls = /* @__PURE__ */ new Map();
3554
+ for (const [prop, decl] of block.decls) {
3555
+ if (CORNER_SET.has(String(prop))) continue;
3556
+ decls.set(prop, decl);
3557
+ }
3558
+ const shorthand = {
3559
+ property: RADIUS,
3560
+ value: fold.value,
3561
+ important: fold.important,
3562
+ relativeToParent: fold.relative,
3563
+ inherited: false
3564
+ // border-radius is never inherited
3565
+ };
3566
+ decls.set(shorthand.property, shorthand);
3567
+ blocks.set(key, { condition: block.condition, decls });
3568
+ }
3569
+ return { blocks };
3570
+ }
3571
+ var borderRadiusShorthand = definePattern({
3572
+ name: "border-radius-shorthand",
3573
+ category: "compress/border-radius-shorthand",
3574
+ safety: 1,
3575
+ doc: {
3576
+ title: "Collapse equal corner radii into border-radius",
3577
+ summary: "An element whose four corner radii (border-*-radius longhands) are all equal is rewritten to the single Tailwind rounded-* utility (border-radius === the four equal corners).",
3578
+ before: '<div class="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg"/>',
3579
+ after: '<div class="rounded-lg"/>',
3580
+ safetyRationale: "`border-radius` is value-identical to four equal corner radii \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3581
+ },
3582
+ rewrite: {
3583
+ rewriteClasses(computed2) {
3584
+ const fold = analyzeRadius(computed2);
3585
+ return fold ? withFoldedRadius(computed2, fold) : null;
3586
+ }
3587
+ },
3588
+ test: {
3589
+ cases: [
3590
+ {
3591
+ // The four equal corner longhands collapse to a `border-radius` decl at the IR level; the
3592
+ // minimizing reverse-emit then picks the single shortest utility (`rounded-lg`) that reproduces
3593
+ // it, replacing the four `rounded-{tl,tr,br,bl}-lg` tokens. `bg-red-200` is preserved.
3594
+ before: '<div className="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg bg-red-200">box</div>',
3595
+ after: '<div className="bg-red-200 rounded-lg">box</div>'
3596
+ }
3597
+ ],
3598
+ // Corners differ (top corners vs bottom corners) → no all-equal collapse.
3599
+ noMatch: ['<div className="rounded-t-lg rounded-b-sm bg-red-200">box</div>']
3600
+ }
3601
+ });
3602
+
3603
+ // ../patterns/src/library/compress/border-shorthand.pattern.ts
3604
+ init_cjs_shims();
3605
+ var WIDTH_SIDES = [
3606
+ "border-top-width",
3607
+ "border-right-width",
3608
+ "border-bottom-width",
3609
+ "border-left-width"
3610
+ ];
3611
+ var WIDTH_SIDE_SET = new Set(WIDTH_SIDES);
3612
+ var BASE_KEY2 = conditionKey(BASE_CONDITION);
3613
+ var BORDER_WIDTH = "border-width";
3614
+ function analyzeWidth(sm) {
3615
+ const block = sm.blocks.get(BASE_KEY2);
3616
+ if (!block) return null;
3617
+ const sides = [];
3618
+ for (const side of WIDTH_SIDES) {
3619
+ const decl = block.decls.get(side);
3620
+ if (!decl) return null;
3621
+ sides.push(decl);
3622
+ }
3623
+ const [top, right, bottom, left] = sides;
3624
+ if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
3625
+ return null;
3626
+ }
3627
+ const tv = String(top.value);
3628
+ const rv = String(right.value);
3629
+ const bv = String(bottom.value);
3630
+ const lv = String(left.value);
3631
+ if (tv !== bv || lv !== rv) return null;
3632
+ const value = tv === lv ? tv : `${tv} ${lv}`;
3633
+ const relative4 = sides.some((d3) => d3.relativeToParent);
3634
+ return { value, important: top.important, relative: relative4 };
3635
+ }
3636
+ function withFoldedWidth(sm, fold) {
3637
+ const blocks = /* @__PURE__ */ new Map();
3638
+ for (const [key, block] of sm.blocks) {
3639
+ if (key !== BASE_KEY2) {
3640
+ blocks.set(key, block);
3641
+ continue;
3642
+ }
3643
+ const decls = /* @__PURE__ */ new Map();
3644
+ for (const [prop, decl] of block.decls) {
3645
+ if (WIDTH_SIDE_SET.has(String(prop))) continue;
3646
+ decls.set(prop, decl);
3647
+ }
3648
+ const shorthand = {
3649
+ property: BORDER_WIDTH,
3650
+ value: fold.value,
3651
+ important: fold.important,
3652
+ relativeToParent: fold.relative,
3653
+ inherited: false
3654
+ // border-width is never inherited
3655
+ };
3656
+ decls.set(shorthand.property, shorthand);
3657
+ blocks.set(key, { condition: block.condition, decls });
3658
+ }
3659
+ return { blocks };
3660
+ }
3661
+ var borderShorthand = definePattern({
3662
+ name: "border-shorthand",
3663
+ category: "compress/border-shorthand",
3664
+ safety: 1,
3665
+ doc: {
3666
+ title: "Collapse border-width longhands to shorthand",
3667
+ summary: "Equal border width on all four sides (or matching x/y pairs) expressed as separate longhand declarations is collapsed to the shortest equivalent border-width shorthand (border-* / border-x-* border-y-*).",
3668
+ before: '<div class="border-t-2 border-r-2 border-b-2 border-l-2"/>',
3669
+ after: '<div class="border-2"/>',
3670
+ safetyRationale: "A value-preserving re-serialization of the same computed border widths (style/color longhands untouched) \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3671
+ },
3672
+ rewrite: {
3673
+ rewriteClasses(computed2) {
3674
+ const fold = analyzeWidth(computed2);
3675
+ return fold ? withFoldedWidth(computed2, fold) : null;
3676
+ }
3677
+ },
3678
+ test: {
3679
+ cases: [
3680
+ {
3681
+ // The four equal width longhands collapse to a `border-width` shorthand at the IR level, and the
3682
+ // minimizing reverse-emit picks the single shortest utility (`border-2`) that reproduces it,
3683
+ // replacing the four `border-{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
3684
+ before: '<div className="border-t-2 border-r-2 border-b-2 border-l-2 bg-red-200">box</div>',
3685
+ after: '<div className="bg-red-200 border-2">box</div>'
3686
+ }
3687
+ ],
3688
+ // Asymmetric widths (top != bottom) cannot fold into a shorthand.
3689
+ noMatch: ['<div className="border-t-2 border-r-4 border-b-8 border-l-4 bg-red-200">box</div>']
3690
+ }
3691
+ });
3692
+
3693
+ // ../patterns/src/library/compress/dedupe-classes.pattern.ts
3694
+ init_cjs_shims();
3695
+ function findRedundantClasses(computed2) {
3696
+ const winners = /* @__PURE__ */ new Set();
3697
+ const shadowed = /* @__PURE__ */ new Set();
3698
+ for (const block of computed2.blocks.values()) {
3699
+ for (const decl of block.decls.values()) {
3700
+ if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
3701
+ for (const o2 of decl.shadowed ?? []) {
3702
+ if (o2.kind === "class") shadowed.add(o2.className);
3703
+ }
3704
+ }
3705
+ }
3706
+ return { winners, shadowed };
3707
+ }
3708
+ var dedupeClasses = definePattern({
3709
+ name: "dedupe-classes",
3710
+ category: "compress/dedupe-classes",
3711
+ safety: 1,
3712
+ doc: {
3713
+ title: "Dedupe fully-overridden class tokens",
3714
+ summary: "Drops class tokens whose every declaration is overridden by a later token resolving to the same property; the surviving token set produces a byte-for-byte identical computed style.",
3715
+ before: '<p class="text-sm text-lg" />',
3716
+ after: '<p class="text-lg" />',
3717
+ safetyRationale: "A fully-overridden token contributes nothing to the computed style in any condition, so removing it changes no pixels \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3718
+ },
2940
3719
  rewrite: {
2941
3720
  dropClasses(computed2, ctx) {
2942
3721
  const { winners, shadowed } = findRedundantClasses(computed2);
@@ -2949,22 +3728,91 @@ var dedupeClasses = pattern({
2949
3728
  return drop;
2950
3729
  }
2951
3730
  },
2952
- examples: [
2953
- {
2954
- // `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
2955
- // records that shadowing in provenance and reports the Tailwind utility as droppable, so the
2956
- // pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
2957
- before: '<p className="text-sm text-lg">Hi</p>',
2958
- after: '<p className="text-lg">Hi</p>'
2959
- },
2960
- {
2961
- // Both tokens win a distinct property (no full override) → nothing to dedupe.
2962
- noMatch: '<p className="text-lg font-bold">Hi</p>'
3731
+ test: {
3732
+ cases: [
3733
+ {
3734
+ // `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
3735
+ // records that shadowing in provenance and reports the Tailwind utility as droppable, so the
3736
+ // pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
3737
+ before: '<p className="text-sm text-lg">Hi</p>',
3738
+ after: '<p className="text-lg">Hi</p>'
3739
+ }
3740
+ ],
3741
+ // Both tokens win a distinct property (no full override) → nothing to dedupe.
3742
+ noMatch: ['<p className="text-lg font-bold">Hi</p>']
3743
+ }
3744
+ });
3745
+
3746
+ // ../patterns/src/library/compress/gap-shorthand.pattern.ts
3747
+ init_cjs_shims();
3748
+ var ROW_GAP = "row-gap";
3749
+ var COLUMN_GAP = "column-gap";
3750
+ var GAP = "gap";
3751
+ var BASE_KEY3 = conditionKey(BASE_CONDITION);
3752
+ function withGapShorthand(sm, gapDecl) {
3753
+ const blocks = /* @__PURE__ */ new Map();
3754
+ for (const [key, block] of sm.blocks) {
3755
+ if (key !== BASE_KEY3) {
3756
+ blocks.set(key, block);
3757
+ continue;
2963
3758
  }
2964
- ]
3759
+ const decls = /* @__PURE__ */ new Map();
3760
+ for (const [prop, decl] of block.decls) {
3761
+ if (prop === ROW_GAP || prop === COLUMN_GAP) continue;
3762
+ decls.set(prop, decl);
3763
+ }
3764
+ decls.set(gapDecl.property, gapDecl);
3765
+ blocks.set(key, { condition: block.condition, decls });
3766
+ }
3767
+ return { blocks };
3768
+ }
3769
+ var gapShorthand = definePattern({
3770
+ name: "gap-shorthand",
3771
+ category: "compress/gap-shorthand",
3772
+ safety: 1,
3773
+ doc: {
3774
+ title: "Collapse equal row/column gap into the `gap` shorthand",
3775
+ summary: "An element whose computed row-gap and column-gap are equal has the two axis longhands collapsed into a single-value `gap` shorthand (Tailwind gap-x-* gap-y-* \u2192 gap-*).",
3776
+ before: '<div style="row-gap:16px;column-gap:16px"/>',
3777
+ after: '<div style="gap:16px"/>',
3778
+ safetyRationale: "A single-value `gap` is value-identical to an equal row-gap+column-gap pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3779
+ },
3780
+ rewrite: {
3781
+ rewriteClasses(computed2) {
3782
+ const base = computed2.blocks.get(BASE_KEY3);
3783
+ if (!base) return null;
3784
+ const rowGap = base.decls.get(ROW_GAP);
3785
+ const colGap = base.decls.get(COLUMN_GAP);
3786
+ if (!rowGap || !colGap) return null;
3787
+ if (rowGap.important !== colGap.important) return null;
3788
+ if (rowGap.value !== colGap.value) return null;
3789
+ const gapDecl = {
3790
+ property: GAP,
3791
+ value: rowGap.value,
3792
+ important: rowGap.important,
3793
+ relativeToParent: rowGap.relativeToParent || colGap.relativeToParent,
3794
+ inherited: false
3795
+ // gap is not an inherited property
3796
+ };
3797
+ return withGapShorthand(computed2, gapDecl);
3798
+ }
3799
+ },
3800
+ test: {
3801
+ cases: [
3802
+ {
3803
+ // Equal row/column gap collapse to a `gap` decl at the IR level; the minimizing reverse-emit
3804
+ // re-expands `gap` to row-gap+column-gap and picks the single utility covering both (`gap-4`),
3805
+ // replacing the `gap-x-4`+`gap-y-4` pair. `bg-red-200` is preserved.
3806
+ before: '<div className="gap-x-4 gap-y-4 bg-red-200">box</div>',
3807
+ after: '<div className="bg-red-200 gap-4">box</div>'
3808
+ }
3809
+ ],
3810
+ // Unequal axes (row-gap != column-gap) have no single-value `gap` equivalent → not collapsed.
3811
+ noMatch: ['<div className="gap-x-2 gap-y-4 bg-red-200">box</div>']
3812
+ }
2965
3813
  });
2966
3814
 
2967
- // ../patterns/src/compress/inset-shorthand.pattern.ts
3815
+ // ../patterns/src/library/compress/inset-shorthand.pattern.ts
2968
3816
  init_cjs_shims();
2969
3817
  var TOP = "top";
2970
3818
  var RIGHT = "right";
@@ -2973,10 +3821,6 @@ var LEFT = "left";
2973
3821
  var INSET = "inset";
2974
3822
  var INSET_BLOCK = "inset-block";
2975
3823
  var INSET_INLINE = "inset-inline";
2976
- var hasRawHtml2 = (node) => {
2977
- const n = node;
2978
- return n.kind === "element" ? n.meta.hasDangerousHtml : false;
2979
- };
2980
3824
  function sameSide(a, b3) {
2981
3825
  return a !== void 0 && b3 !== void 0 && a.value === b3.value && a.important === b3.important;
2982
3826
  }
@@ -2991,7 +3835,7 @@ function withBaseDecls(src, baseDecls) {
2991
3835
  }
2992
3836
  return { blocks };
2993
3837
  }
2994
- var insetShorthand = pattern({
3838
+ var insetShorthand = definePattern({
2995
3839
  name: "inset-shorthand",
2996
3840
  category: "compress/inset-shorthand",
2997
3841
  safety: 2,
@@ -3000,17 +3844,7 @@ var insetShorthand = pattern({
3000
3844
  summary: "top/right/bottom/left set to one value collapse to `inset`; a matching top/bottom or left/right pair collapses to `inset-block` / `inset-inline` (Tailwind inset-y / inset-x).",
3001
3845
  before: '<div style="top:10px;right:10px;bottom:10px;left:10px"/>',
3002
3846
  after: '<div style="inset:10px"/>',
3003
- safetyRationale: "Meaning-preserving shorthand compaction; the element is not a combinator subject and carries no ref/handlers/dynamic children/raw HTML, so neither selector matching nor behaviour changes."
3004
- },
3005
- match: {
3006
- where: [
3007
- not(hasRef),
3008
- not(hasEventHandlers),
3009
- not(hasDynamicChildren),
3010
- not(hasRawHtml2),
3011
- not(hasDynamicClasses),
3012
- not(targetedByCombinator)
3013
- ]
3847
+ safetyRationale: "Meaning-preserving inset shorthand compaction \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3014
3848
  },
3015
3849
  rewrite: {
3016
3850
  rewriteClasses(computed2) {
@@ -3046,22 +3880,22 @@ var insetShorthand = pattern({
3046
3880
  return withBaseDecls(computed2, next);
3047
3881
  }
3048
3882
  },
3049
- examples: [
3050
- {
3051
- // The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
3052
- // minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
3053
- // covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
3054
- before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
3055
- after: '<div className="bg-red-200 inset-0">box</div>'
3056
- },
3057
- {
3058
- // No matching inset pair (all four distinct) → nothing collapses.
3059
- noMatch: '<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>'
3060
- }
3061
- ]
3883
+ test: {
3884
+ cases: [
3885
+ {
3886
+ // The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
3887
+ // minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
3888
+ // covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
3889
+ before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
3890
+ after: '<div className="bg-red-200 inset-0">box</div>'
3891
+ }
3892
+ ],
3893
+ // No matching inset pair (all four distinct) → nothing collapses.
3894
+ noMatch: ['<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>']
3895
+ }
3062
3896
  });
3063
3897
 
3064
- // ../patterns/src/compress/margin-shorthand.pattern.ts
3898
+ // ../patterns/src/library/compress/margin-shorthand.pattern.ts
3065
3899
  init_cjs_shims();
3066
3900
  var MARGIN_SIDES = [
3067
3901
  "margin-top",
@@ -3070,12 +3904,7 @@ var MARGIN_SIDES = [
3070
3904
  "margin-left"
3071
3905
  ];
3072
3906
  var MARGIN_SIDE_SET = new Set(MARGIN_SIDES);
3073
- var BASE_KEY = conditionKey(BASE_CONDITION);
3074
- function asElement3(node) {
3075
- const n = node;
3076
- return n.kind === "element" ? n : null;
3077
- }
3078
- var hasDangerousHtml2 = (node) => asElement3(node)?.meta.hasDangerousHtml ?? false;
3907
+ var BASE_KEY4 = conditionKey(BASE_CONDITION);
3079
3908
  function collapseMarginValue(top, right, bottom, left) {
3080
3909
  if (right === left) {
3081
3910
  if (top === bottom) {
@@ -3088,7 +3917,7 @@ function collapseMarginValue(top, right, bottom, left) {
3088
3917
  function withFoldedMargin(sm, marginDecl) {
3089
3918
  const blocks = /* @__PURE__ */ new Map();
3090
3919
  for (const [key, block] of sm.blocks) {
3091
- if (key !== BASE_KEY) {
3920
+ if (key !== BASE_KEY4) {
3092
3921
  blocks.set(key, block);
3093
3922
  continue;
3094
3923
  }
@@ -3101,7 +3930,7 @@ function withFoldedMargin(sm, marginDecl) {
3101
3930
  }
3102
3931
  return { blocks };
3103
3932
  }
3104
- var marginShorthand = pattern({
3933
+ var marginShorthand = definePattern({
3105
3934
  name: "margin-shorthand",
3106
3935
  category: "compress/margin-shorthand",
3107
3936
  safety: 2,
@@ -3110,177 +3939,567 @@ var marginShorthand = pattern({
3110
3939
  summary: "An element with margin-top/right/bottom/left all set has them collapsed into the shortest legal `margin` shorthand (the m / mx / my forms); meaning is preserved, declaration count drops.",
3111
3940
  before: '<div style="margin-top:8px;margin-right:8px;margin-bottom:8px;margin-left:8px"/>',
3112
3941
  after: '<div style="margin:8px"/>',
3113
- safetyRationale: "Pure representation change (no pixels move); skips nodes with ref/handlers/dynamic children/raw html, dynamic class segments, or combinator-subject selectors."
3942
+ safetyRationale: "A pure representation change of the same computed margins (no pixels move) \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3114
3943
  },
3115
- match: {
3116
- where: [
3117
- not(hasRef),
3118
- not(hasEventHandlers),
3119
- not(hasDynamicChildren),
3120
- not(hasDynamicClasses),
3121
- not(hasDangerousHtml2),
3122
- not(targetedByCombinator)
3123
- ]
3944
+ rewrite: {
3945
+ rewriteClasses(computed2) {
3946
+ const base = computed2.blocks.get(BASE_KEY4);
3947
+ if (!base) return null;
3948
+ const sides = MARGIN_SIDES.map((p2) => base.decls.get(p2));
3949
+ if (sides.some((d3) => d3 === void 0)) return null;
3950
+ const [mt, mr, mb, ml] = sides;
3951
+ if (mt.important || mr.important || mb.important || ml.important) return null;
3952
+ const value = collapseMarginValue(
3953
+ String(mt.value),
3954
+ String(mr.value),
3955
+ String(mb.value),
3956
+ String(ml.value)
3957
+ );
3958
+ const marginDecl = {
3959
+ property: "margin",
3960
+ value,
3961
+ important: false,
3962
+ relativeToParent: mt.relativeToParent || mr.relativeToParent || mb.relativeToParent || ml.relativeToParent,
3963
+ inherited: false
3964
+ // margin is not an inherited property
3965
+ };
3966
+ return withFoldedMargin(computed2, marginDecl);
3967
+ }
3968
+ },
3969
+ test: {
3970
+ cases: [
3971
+ {
3972
+ // The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
3973
+ // minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
3974
+ // the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
3975
+ before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
3976
+ after: '<div className="bg-red-200 m-2">box</div>'
3977
+ }
3978
+ ],
3979
+ // Only two margin sides set → the four-longhand `margin` collapse does not apply.
3980
+ noMatch: ['<div className="mt-2 mb-2 bg-red-200">box</div>']
3981
+ }
3982
+ });
3983
+
3984
+ // ../patterns/src/library/compress/overflow-shorthand.pattern.ts
3985
+ init_cjs_shims();
3986
+ var OVERFLOW_X = "overflow-x";
3987
+ var OVERFLOW_Y = "overflow-y";
3988
+ var OVERFLOW = "overflow";
3989
+ var BASE_KEY5 = conditionKey(BASE_CONDITION);
3990
+ function withOverflowShorthand(sm, overflowDecl) {
3991
+ const blocks = /* @__PURE__ */ new Map();
3992
+ for (const [key, block] of sm.blocks) {
3993
+ if (key !== BASE_KEY5) {
3994
+ blocks.set(key, block);
3995
+ continue;
3996
+ }
3997
+ const decls = /* @__PURE__ */ new Map();
3998
+ for (const [prop, decl] of block.decls) {
3999
+ if (prop === OVERFLOW_X || prop === OVERFLOW_Y) continue;
4000
+ decls.set(prop, decl);
4001
+ }
4002
+ decls.set(overflowDecl.property, overflowDecl);
4003
+ blocks.set(key, { condition: block.condition, decls });
4004
+ }
4005
+ return { blocks };
4006
+ }
4007
+ var overflowShorthand = definePattern({
4008
+ name: "overflow-shorthand",
4009
+ category: "compress/overflow-shorthand",
4010
+ safety: 1,
4011
+ doc: {
4012
+ title: "Collapse equal overflow axes into the `overflow` shorthand",
4013
+ summary: "An element whose computed overflow-x and overflow-y are equal has the two axis longhands collapsed into a single `overflow` shorthand (Tailwind overflow-x-* overflow-y-* \u2192 overflow-*).",
4014
+ before: '<div style="overflow-x:auto;overflow-y:auto"/>',
4015
+ after: '<div style="overflow:auto"/>',
4016
+ safetyRationale: "A single-keyword `overflow` is value-identical to equal overflow-x+overflow-y \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
4017
+ },
4018
+ rewrite: {
4019
+ rewriteClasses(computed2) {
4020
+ const base = computed2.blocks.get(BASE_KEY5);
4021
+ if (!base) return null;
4022
+ const overflowX = base.decls.get(OVERFLOW_X);
4023
+ const overflowY = base.decls.get(OVERFLOW_Y);
4024
+ if (!overflowX || !overflowY) return null;
4025
+ if (overflowX.important !== overflowY.important) return null;
4026
+ if (overflowX.value !== overflowY.value) return null;
4027
+ const overflowDecl = {
4028
+ property: OVERFLOW,
4029
+ value: overflowX.value,
4030
+ important: overflowX.important,
4031
+ relativeToParent: overflowX.relativeToParent || overflowY.relativeToParent,
4032
+ inherited: false
4033
+ // overflow is not an inherited property
4034
+ };
4035
+ return withOverflowShorthand(computed2, overflowDecl);
4036
+ }
4037
+ },
4038
+ test: {
4039
+ cases: [
4040
+ {
4041
+ // Equal overflow axes collapse to an `overflow` decl at the IR level; the minimizing
4042
+ // reverse-emit picks the single utility covering both (`overflow-auto`), replacing the
4043
+ // `overflow-x-auto`+`overflow-y-auto` pair. `bg-red-200` is preserved.
4044
+ before: '<div className="overflow-x-auto overflow-y-auto bg-red-200">box</div>',
4045
+ after: '<div className="bg-red-200 overflow-auto">box</div>'
4046
+ }
4047
+ ],
4048
+ // Mismatched axes (overflow-x != overflow-y) have no single-keyword equivalent → not collapsed.
4049
+ noMatch: ['<div className="overflow-x-auto overflow-y-hidden bg-red-200">box</div>']
4050
+ }
4051
+ });
4052
+
4053
+ // ../patterns/src/library/compress/overscroll-behavior-shorthand.pattern.ts
4054
+ init_cjs_shims();
4055
+ var OVERSCROLL_X = "overscroll-behavior-x";
4056
+ var OVERSCROLL_Y = "overscroll-behavior-y";
4057
+ var OVERSCROLL = "overscroll-behavior";
4058
+ var BASE_KEY6 = conditionKey(BASE_CONDITION);
4059
+ var NON_COLLAPSIBLE_VALUES2 = /* @__PURE__ */ new Set([
4060
+ "initial",
4061
+ "inherit",
4062
+ "unset",
4063
+ "revert",
4064
+ "revert-layer"
4065
+ ]);
4066
+ function withOverscrollShorthand(sm, shorthand) {
4067
+ const blocks = /* @__PURE__ */ new Map();
4068
+ for (const [key, block] of sm.blocks) {
4069
+ if (key !== BASE_KEY6) {
4070
+ blocks.set(key, block);
4071
+ continue;
4072
+ }
4073
+ const decls = /* @__PURE__ */ new Map();
4074
+ for (const [prop, decl] of block.decls) {
4075
+ if (prop === OVERSCROLL_X || prop === OVERSCROLL_Y) continue;
4076
+ decls.set(prop, decl);
4077
+ }
4078
+ decls.set(shorthand.property, shorthand);
4079
+ blocks.set(key, { condition: block.condition, decls });
4080
+ }
4081
+ return { blocks };
4082
+ }
4083
+ var overscrollBehaviorShorthand = definePattern({
4084
+ name: "overscroll-behavior-shorthand",
4085
+ category: "compress/overscroll-behavior-shorthand",
4086
+ safety: 1,
4087
+ doc: {
4088
+ title: "Collapse equal overscroll-behavior axes into overscroll-behavior",
4089
+ summary: "An element whose computed overscroll-behavior-x and overscroll-behavior-y are equal has the two axis longhands collapsed into a single `overscroll-behavior` shorthand (Tailwind overscroll-x-* overscroll-y-* \u2192 overscroll-*).",
4090
+ before: '<div style="overscroll-behavior-x:contain;overscroll-behavior-y:contain"/>',
4091
+ after: '<div class="overscroll-contain"/>',
4092
+ safetyRationale: "`overscroll-behavior` is value-identical to an equal x+y axis pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
4093
+ },
4094
+ rewrite: {
4095
+ rewriteClasses(computed2) {
4096
+ const base = computed2.blocks.get(BASE_KEY6);
4097
+ if (!base) return null;
4098
+ const x2 = base.decls.get(OVERSCROLL_X);
4099
+ const y3 = base.decls.get(OVERSCROLL_Y);
4100
+ if (!x2 || !y3) return null;
4101
+ if (x2.important !== y3.important) return null;
4102
+ const value = String(x2.value);
4103
+ if (NON_COLLAPSIBLE_VALUES2.has(value)) return null;
4104
+ if (value !== String(y3.value)) return null;
4105
+ const shorthand = {
4106
+ property: OVERSCROLL,
4107
+ value: x2.value,
4108
+ important: x2.important,
4109
+ relativeToParent: x2.relativeToParent || y3.relativeToParent,
4110
+ inherited: false
4111
+ // overscroll-behavior is not an inherited property
4112
+ };
4113
+ return withOverscrollShorthand(computed2, shorthand);
4114
+ }
4115
+ },
4116
+ test: {
4117
+ cases: [
4118
+ {
4119
+ // Equal x/y axes collapse to an `overscroll-behavior` decl at the IR level; the minimizing
4120
+ // reverse-emit picks the single utility covering both (`overscroll-contain`), replacing the
4121
+ // `overscroll-x-contain`+`overscroll-y-contain` pair. `bg-red-200` is preserved.
4122
+ before: '<div className="overscroll-x-contain overscroll-y-contain bg-red-200">box</div>',
4123
+ after: '<div className="bg-red-200 overscroll-contain">box</div>'
4124
+ }
4125
+ ],
4126
+ // Axes differ (x != y) → no equal-axis collapse.
4127
+ noMatch: ['<div className="overscroll-x-contain overscroll-y-auto bg-red-200">box</div>']
4128
+ }
4129
+ });
4130
+
4131
+ // ../patterns/src/library/compress/padding-shorthand.pattern.ts
4132
+ init_cjs_shims();
4133
+ var PADDING_SIDES = [
4134
+ "padding-top",
4135
+ "padding-right",
4136
+ "padding-bottom",
4137
+ "padding-left"
4138
+ ];
4139
+ var PADDING_SIDE_SET = new Set(PADDING_SIDES);
4140
+ var BASE_KEY7 = conditionKey(BASE_CONDITION);
4141
+ function analyzePadding(sm) {
4142
+ const block = sm.blocks.get(BASE_KEY7);
4143
+ if (!block) return null;
4144
+ const sides = [];
4145
+ for (const side of PADDING_SIDES) {
4146
+ const decl = block.decls.get(side);
4147
+ if (!decl) return null;
4148
+ sides.push(decl);
4149
+ }
4150
+ const [top, right, bottom, left] = sides;
4151
+ if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
4152
+ return null;
4153
+ }
4154
+ const tv = String(top.value);
4155
+ const rv = String(right.value);
4156
+ const bv = String(bottom.value);
4157
+ const lv = String(left.value);
4158
+ if (tv !== bv || lv !== rv) return null;
4159
+ const value = tv === lv ? tv : `${tv} ${lv}`;
4160
+ const relative4 = sides.some((d3) => d3.relativeToParent);
4161
+ return { value, important: top.important, relative: relative4 };
4162
+ }
4163
+ function withFoldedPadding(sm, fold) {
4164
+ const blocks = /* @__PURE__ */ new Map();
4165
+ for (const [key, block] of sm.blocks) {
4166
+ if (key !== BASE_KEY7) {
4167
+ blocks.set(key, block);
4168
+ continue;
4169
+ }
4170
+ const decls = /* @__PURE__ */ new Map();
4171
+ for (const [prop, decl] of block.decls) {
4172
+ if (PADDING_SIDE_SET.has(String(prop))) continue;
4173
+ decls.set(prop, decl);
4174
+ }
4175
+ const shorthand = {
4176
+ property: "padding",
4177
+ value: fold.value,
4178
+ important: fold.important,
4179
+ relativeToParent: fold.relative,
4180
+ inherited: false
4181
+ // padding is never inherited
4182
+ };
4183
+ decls.set(shorthand.property, shorthand);
4184
+ blocks.set(key, { condition: block.condition, decls });
4185
+ }
4186
+ return { blocks };
4187
+ }
4188
+ var paddingShorthand = definePattern({
4189
+ name: "padding-shorthand",
4190
+ category: "compress/padding-shorthand",
4191
+ safety: 1,
4192
+ doc: {
4193
+ title: "Collapse padding longhands to shorthand",
4194
+ summary: "Equal padding on all four sides (or matching x/y pairs) expressed as separate longhand declarations is collapsed to the shortest equivalent padding shorthand (p-* / px-* py-*).",
4195
+ before: '<div class="pt-4 pr-4 pb-4 pl-4"/>',
4196
+ after: '<div class="p-4"/>',
4197
+ safetyRationale: "A value-preserving re-serialization of the same computed padding on the same node \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
4198
+ },
4199
+ rewrite: {
4200
+ rewriteClasses(computed2) {
4201
+ const fold = analyzePadding(computed2);
4202
+ return fold ? withFoldedPadding(computed2, fold) : null;
4203
+ }
4204
+ },
4205
+ test: {
4206
+ cases: [
4207
+ {
4208
+ // The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
4209
+ // minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
4210
+ // replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
4211
+ before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
4212
+ after: '<div className="bg-red-200 p-4">box</div>'
4213
+ },
4214
+ {
4215
+ // A dynamic `{x}` child no longer blocks compress: only the element's OWN class tokens are
4216
+ // rewritten (px-4 py-4 → p-4); the dynamic child is untouched by a class-only change. This is
4217
+ // the real-app common case (most elements have dynamic content).
4218
+ before: '<div className="px-4 py-4">{x}</div>',
4219
+ after: '<div className="p-4">{x}</div>'
4220
+ }
4221
+ ],
4222
+ // Asymmetric padding (top != bottom) cannot fold into a shorthand → left unchanged.
4223
+ noMatch: ['<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>']
4224
+ }
4225
+ });
4226
+
4227
+ // ../patterns/src/library/compress/place-shorthand.pattern.ts
4228
+ init_cjs_shims();
4229
+ var ALIGN_ITEMS = "align-items";
4230
+ var JUSTIFY_ITEMS = "justify-items";
4231
+ var PLACE_ITEMS = "place-items";
4232
+ var ALIGN_CONTENT = "align-content";
4233
+ var JUSTIFY_CONTENT = "justify-content";
4234
+ var PLACE_CONTENT = "place-content";
4235
+ var BASE_KEY8 = conditionKey(BASE_CONDITION);
4236
+ function samePair(a, b3) {
4237
+ return a !== void 0 && b3 !== void 0 && a.value === b3.value && a.important === b3.important;
4238
+ }
4239
+ function placeDecl(property, align) {
4240
+ return {
4241
+ property,
4242
+ value: align.value,
4243
+ important: align.important,
4244
+ relativeToParent: false,
4245
+ // alignment keywords (center/start/stretch/…) are not length-relative
4246
+ inherited: false
4247
+ // none of the place-* alignment properties are inherited
4248
+ };
4249
+ }
4250
+ function withBaseDecls2(sm, baseDecls) {
4251
+ const blocks = /* @__PURE__ */ new Map();
4252
+ for (const [key, block] of sm.blocks) {
4253
+ const decls = key === BASE_KEY8 ? new Map(baseDecls) : block.decls;
4254
+ blocks.set(key, { condition: block.condition, decls });
4255
+ }
4256
+ return { blocks };
4257
+ }
4258
+ var placeShorthand = definePattern({
4259
+ name: "place-shorthand",
4260
+ category: "compress/place-shorthand",
4261
+ safety: 1,
4262
+ doc: {
4263
+ title: "Collapse matching alignment pairs into `place-*` shorthands",
4264
+ summary: "When align-items equals justify-items they collapse to `place-items`; when align-content equals justify-content they collapse to `place-content`. The two collapses are independent.",
4265
+ before: '<div style="align-items:center;justify-items:center"/>',
4266
+ after: '<div style="place-items:center"/>',
4267
+ safetyRationale: "A `place-*` shorthand is value-identical to its equal align/justify pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
4268
+ },
4269
+ rewrite: {
4270
+ rewriteClasses(computed2) {
4271
+ const base = computed2.blocks.get(BASE_KEY8);
4272
+ if (!base) return null;
4273
+ const alignItems = base.decls.get(ALIGN_ITEMS);
4274
+ const justifyItems = base.decls.get(JUSTIFY_ITEMS);
4275
+ const alignContent = base.decls.get(ALIGN_CONTENT);
4276
+ const justifyContent = base.decls.get(JUSTIFY_CONTENT);
4277
+ const next = new Map(base.decls);
4278
+ let collapsed = false;
4279
+ if (samePair(alignItems, justifyItems)) {
4280
+ next.delete(ALIGN_ITEMS);
4281
+ next.delete(JUSTIFY_ITEMS);
4282
+ next.set(PLACE_ITEMS, placeDecl(PLACE_ITEMS, alignItems));
4283
+ collapsed = true;
4284
+ }
4285
+ if (samePair(alignContent, justifyContent)) {
4286
+ next.delete(ALIGN_CONTENT);
4287
+ next.delete(JUSTIFY_CONTENT);
4288
+ next.set(PLACE_CONTENT, placeDecl(PLACE_CONTENT, alignContent));
4289
+ collapsed = true;
4290
+ }
4291
+ if (!collapsed) return null;
4292
+ return withBaseDecls2(computed2, next);
4293
+ }
4294
+ },
4295
+ test: {
4296
+ cases: [
4297
+ {
4298
+ // The matching items pair collapses to a `place-items` decl at the IR level; the minimizing
4299
+ // reverse-emit picks the single utility covering both (`place-items-center`), replacing the
4300
+ // `items-center`+`justify-items-center` pair. `bg-red-200` is preserved.
4301
+ before: '<div className="items-center justify-items-center bg-red-200">box</div>',
4302
+ after: '<div className="bg-red-200 place-items-center">box</div>'
4303
+ }
4304
+ ],
4305
+ // Mismatched alignment (align-items != justify-items, no content pair) → nothing collapses.
4306
+ noMatch: ['<div className="items-center justify-items-start bg-red-200">box</div>']
4307
+ }
4308
+ });
4309
+
4310
+ // ../patterns/src/library/compress/scroll-margin-shorthand.pattern.ts
4311
+ init_cjs_shims();
4312
+ var SCROLL_MARGIN_SIDES = [
4313
+ "scroll-margin-top",
4314
+ "scroll-margin-right",
4315
+ "scroll-margin-bottom",
4316
+ "scroll-margin-left"
4317
+ ];
4318
+ var SIDE_SET = new Set(SCROLL_MARGIN_SIDES);
4319
+ var BASE_KEY9 = conditionKey(BASE_CONDITION);
4320
+ var SCROLL_MARGIN = "scroll-margin";
4321
+ var NON_COLLAPSIBLE_VALUES3 = /* @__PURE__ */ new Set([
4322
+ "initial",
4323
+ "inherit",
4324
+ "unset",
4325
+ "revert",
4326
+ "revert-layer"
4327
+ ]);
4328
+ function analyzeScrollMargin(sm) {
4329
+ const block = sm.blocks.get(BASE_KEY9);
4330
+ if (!block) return null;
4331
+ const sides = [];
4332
+ for (const side of SCROLL_MARGIN_SIDES) {
4333
+ const decl = block.decls.get(side);
4334
+ if (!decl) return null;
4335
+ sides.push(decl);
4336
+ }
4337
+ const important = sides[0].important;
4338
+ if (!sides.every((d3) => d3.important === important)) return null;
4339
+ const value = String(sides[0].value);
4340
+ if (NON_COLLAPSIBLE_VALUES3.has(value)) return null;
4341
+ if (!sides.every((d3) => String(d3.value) === value)) return null;
4342
+ const relative4 = sides.some((d3) => d3.relativeToParent);
4343
+ return { value, important, relative: relative4 };
4344
+ }
4345
+ function withFoldedScrollMargin(sm, fold) {
4346
+ const blocks = /* @__PURE__ */ new Map();
4347
+ for (const [key, block] of sm.blocks) {
4348
+ if (key !== BASE_KEY9) {
4349
+ blocks.set(key, block);
4350
+ continue;
4351
+ }
4352
+ const decls = /* @__PURE__ */ new Map();
4353
+ for (const [prop, decl] of block.decls) {
4354
+ if (SIDE_SET.has(String(prop))) continue;
4355
+ decls.set(prop, decl);
4356
+ }
4357
+ const shorthand = {
4358
+ property: SCROLL_MARGIN,
4359
+ value: fold.value,
4360
+ important: fold.important,
4361
+ relativeToParent: fold.relative,
4362
+ inherited: false
4363
+ // scroll-margin is never inherited
4364
+ };
4365
+ decls.set(shorthand.property, shorthand);
4366
+ blocks.set(key, { condition: block.condition, decls });
4367
+ }
4368
+ return { blocks };
4369
+ }
4370
+ var scrollMarginShorthand = definePattern({
4371
+ name: "scroll-margin-shorthand",
4372
+ category: "compress/scroll-margin-shorthand",
4373
+ safety: 1,
4374
+ doc: {
4375
+ title: "Collapse equal scroll-margin sides into scroll-margin",
4376
+ summary: "An element whose four scroll-margin sides are all equal is rewritten to the single Tailwind scroll-m-* utility (scroll-margin === the four equal sides).",
4377
+ before: '<div class="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4"/>',
4378
+ after: '<div class="scroll-m-4"/>',
4379
+ safetyRationale: "`scroll-margin` is value-identical to four equal scroll-margin sides \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3124
4380
  },
3125
4381
  rewrite: {
3126
4382
  rewriteClasses(computed2) {
3127
- const base = computed2.blocks.get(BASE_KEY);
3128
- if (!base) return null;
3129
- const sides = MARGIN_SIDES.map((p2) => base.decls.get(p2));
3130
- if (sides.some((d3) => d3 === void 0)) return null;
3131
- const [mt, mr, mb, ml] = sides;
3132
- if (mt.important || mr.important || mb.important || ml.important) return null;
3133
- const value = collapseMarginValue(
3134
- String(mt.value),
3135
- String(mr.value),
3136
- String(mb.value),
3137
- String(ml.value)
3138
- );
3139
- const marginDecl = {
3140
- property: "margin",
3141
- value,
3142
- important: false,
3143
- relativeToParent: mt.relativeToParent || mr.relativeToParent || mb.relativeToParent || ml.relativeToParent,
3144
- inherited: false
3145
- // margin is not an inherited property
3146
- };
3147
- return withFoldedMargin(computed2, marginDecl);
4383
+ const fold = analyzeScrollMargin(computed2);
4384
+ return fold ? withFoldedScrollMargin(computed2, fold) : null;
3148
4385
  }
3149
4386
  },
3150
- examples: [
3151
- {
3152
- // The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
3153
- // minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
3154
- // the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
3155
- before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
3156
- after: '<div className="bg-red-200 m-2">box</div>'
3157
- },
3158
- {
3159
- // Only two margin sides set → the four-longhand `margin` collapse does not apply.
3160
- noMatch: '<div className="mt-2 mb-2 bg-red-200">box</div>'
3161
- }
3162
- ]
4387
+ test: {
4388
+ cases: [
4389
+ {
4390
+ // The four equal scroll-margin longhands collapse to a `scroll-margin` decl at the IR level; the
4391
+ // minimizing reverse-emit then picks the single shortest utility (`scroll-m-4`) that reproduces
4392
+ // it, replacing the four `scroll-m{t,r,b,l}-4` tokens. `bg-red-200` is preserved.
4393
+ before: '<div className="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4 bg-red-200">box</div>',
4394
+ after: '<div className="bg-red-200 scroll-m-4">box</div>'
4395
+ }
4396
+ ],
4397
+ // Sides differ (top != bottom) → no all-equal collapse.
4398
+ noMatch: ['<div className="scroll-mt-2 scroll-mr-4 scroll-mb-8 scroll-ml-4 bg-red-200">box</div>']
4399
+ }
3163
4400
  });
3164
4401
 
3165
- // ../patterns/src/compress/padding-shorthand.pattern.ts
4402
+ // ../patterns/src/library/compress/scroll-padding-shorthand.pattern.ts
3166
4403
  init_cjs_shims();
3167
- var PADDING_SIDES = [
3168
- "padding-top",
3169
- "padding-right",
3170
- "padding-bottom",
3171
- "padding-left"
4404
+ var SCROLL_PADDING_SIDES = [
4405
+ "scroll-padding-top",
4406
+ "scroll-padding-right",
4407
+ "scroll-padding-bottom",
4408
+ "scroll-padding-left"
3172
4409
  ];
3173
- var PADDING_SIDE_SET = new Set(PADDING_SIDES);
3174
- var BASE_KEY2 = conditionKey(BASE_CONDITION);
3175
- function analyzePadding(sm) {
3176
- const block = sm.blocks.get(BASE_KEY2);
4410
+ var SIDE_SET2 = new Set(SCROLL_PADDING_SIDES);
4411
+ var BASE_KEY10 = conditionKey(BASE_CONDITION);
4412
+ var SCROLL_PADDING = "scroll-padding";
4413
+ var NON_COLLAPSIBLE_VALUES4 = /* @__PURE__ */ new Set([
4414
+ "initial",
4415
+ "inherit",
4416
+ "unset",
4417
+ "revert",
4418
+ "revert-layer"
4419
+ ]);
4420
+ function analyzeScrollPadding(sm) {
4421
+ const block = sm.blocks.get(BASE_KEY10);
3177
4422
  if (!block) return null;
3178
4423
  const sides = [];
3179
- for (const side of PADDING_SIDES) {
4424
+ for (const side of SCROLL_PADDING_SIDES) {
3180
4425
  const decl = block.decls.get(side);
3181
4426
  if (!decl) return null;
3182
4427
  sides.push(decl);
3183
4428
  }
3184
- const [top, right, bottom, left] = sides;
3185
- if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
3186
- return null;
3187
- }
3188
- const tv = String(top.value);
3189
- const rv = String(right.value);
3190
- const bv = String(bottom.value);
3191
- const lv = String(left.value);
3192
- if (tv !== bv || lv !== rv) return null;
3193
- const value = tv === lv ? tv : `${tv} ${lv}`;
3194
- const relative3 = sides.some((d3) => d3.relativeToParent);
3195
- return { value, important: top.important, relative: relative3 };
4429
+ const important = sides[0].important;
4430
+ if (!sides.every((d3) => d3.important === important)) return null;
4431
+ const value = String(sides[0].value);
4432
+ if (NON_COLLAPSIBLE_VALUES4.has(value)) return null;
4433
+ if (!sides.every((d3) => String(d3.value) === value)) return null;
4434
+ const relative4 = sides.some((d3) => d3.relativeToParent);
4435
+ return { value, important, relative: relative4 };
3196
4436
  }
3197
- var isInert = (node) => {
3198
- const n = node;
3199
- if (n.kind !== "element") return false;
3200
- const el = n;
3201
- return !el.meta.hasDangerousHtml && !el.meta.hasSpreadAttrs && !el.isComponent;
3202
- };
3203
- function withFoldedPadding(sm, fold) {
4437
+ function withFoldedScrollPadding(sm, fold) {
3204
4438
  const blocks = /* @__PURE__ */ new Map();
3205
4439
  for (const [key, block] of sm.blocks) {
3206
- if (key !== BASE_KEY2) {
4440
+ if (key !== BASE_KEY10) {
3207
4441
  blocks.set(key, block);
3208
4442
  continue;
3209
4443
  }
3210
4444
  const decls = /* @__PURE__ */ new Map();
3211
4445
  for (const [prop, decl] of block.decls) {
3212
- if (PADDING_SIDE_SET.has(String(prop))) continue;
4446
+ if (SIDE_SET2.has(String(prop))) continue;
3213
4447
  decls.set(prop, decl);
3214
4448
  }
3215
4449
  const shorthand = {
3216
- property: "padding",
4450
+ property: SCROLL_PADDING,
3217
4451
  value: fold.value,
3218
4452
  important: fold.important,
3219
4453
  relativeToParent: fold.relative,
3220
4454
  inherited: false
3221
- // padding is never inherited
4455
+ // scroll-padding is never inherited
3222
4456
  };
3223
4457
  decls.set(shorthand.property, shorthand);
3224
4458
  blocks.set(key, { condition: block.condition, decls });
3225
4459
  }
3226
4460
  return { blocks };
3227
4461
  }
3228
- var paddingShorthand = pattern({
3229
- name: "padding-shorthand",
3230
- category: "compress/padding-shorthand",
4462
+ var scrollPaddingShorthand = definePattern({
4463
+ name: "scroll-padding-shorthand",
4464
+ category: "compress/scroll-padding-shorthand",
3231
4465
  safety: 1,
3232
4466
  doc: {
3233
- title: "Collapse padding longhands to shorthand",
3234
- summary: "Equal padding on all four sides (or matching x/y pairs) expressed as separate longhand declarations is collapsed to the shortest equivalent padding shorthand (p-* / px-* py-*).",
3235
- before: '<div class="pt-4 pr-4 pb-4 pl-4"/>',
3236
- after: '<div class="p-4"/>',
3237
- safetyRationale: "A value-preserving re-serialization of the same computed styles on the same node; it skips nodes with ref/handlers/dynamic children/dynamic classes/dangerous html and combinator subjects, so no JS identity, behaviour, or project selector is disturbed."
3238
- },
3239
- match: {
3240
- where: [
3241
- not(hasRef),
3242
- not(hasEventHandlers),
3243
- not(hasDynamicChildren),
3244
- not(hasDynamicClasses),
3245
- not(targetedByCombinator),
3246
- isInert
3247
- ]
4467
+ title: "Collapse equal scroll-padding sides into scroll-padding",
4468
+ summary: "An element whose four scroll-padding sides are all equal is rewritten to the single Tailwind scroll-p-* utility (scroll-padding === the four equal sides).",
4469
+ before: '<div class="scroll-pt-4 scroll-pr-4 scroll-pb-4 scroll-pl-4"/>',
4470
+ after: '<div class="scroll-p-4"/>',
4471
+ safetyRationale: "`scroll-padding` is value-identical to four equal scroll-padding sides \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3248
4472
  },
3249
4473
  rewrite: {
3250
4474
  rewriteClasses(computed2) {
3251
- const fold = analyzePadding(computed2);
3252
- return fold ? withFoldedPadding(computed2, fold) : null;
4475
+ const fold = analyzeScrollPadding(computed2);
4476
+ return fold ? withFoldedScrollPadding(computed2, fold) : null;
3253
4477
  }
3254
4478
  },
3255
- examples: [
3256
- {
3257
- // The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
3258
- // minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
3259
- // replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
3260
- before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
3261
- after: '<div className="bg-red-200 p-4">box</div>'
3262
- },
3263
- {
3264
- // Asymmetric padding (top != bottom) cannot fold into a shorthand.
3265
- noMatch: '<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>'
3266
- }
3267
- ]
4479
+ test: {
4480
+ cases: [
4481
+ {
4482
+ // The four equal scroll-padding longhands collapse to a `scroll-padding` decl at the IR level;
4483
+ // the minimizing reverse-emit then picks the single shortest utility (`scroll-p-4`) that
4484
+ // reproduces it, replacing the four `scroll-p{t,r,b,l}-4` tokens. `bg-red-200` is preserved.
4485
+ before: '<div className="scroll-pt-4 scroll-pr-4 scroll-pb-4 scroll-pl-4 bg-red-200">box</div>',
4486
+ after: '<div className="bg-red-200 scroll-p-4">box</div>'
4487
+ }
4488
+ ],
4489
+ // Sides differ (top != bottom) → no all-equal collapse.
4490
+ noMatch: ['<div className="scroll-pt-2 scroll-pr-4 scroll-pb-8 scroll-pl-4 bg-red-200">box</div>']
4491
+ }
3268
4492
  });
3269
4493
 
3270
- // ../patterns/src/compress/size-shorthand.pattern.ts
4494
+ // ../patterns/src/library/compress/size-shorthand.pattern.ts
3271
4495
  init_cjs_shims();
3272
4496
  var WIDTH = "width";
3273
4497
  var HEIGHT = "height";
3274
4498
  var SIZE = "size";
3275
- var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set(["auto", "initial", "unset"]);
3276
- function asElement4(node) {
3277
- const n = node;
3278
- return n.kind === "element" ? n : null;
3279
- }
4499
+ var NON_COLLAPSIBLE_VALUES5 = /* @__PURE__ */ new Set(["auto", "initial", "unset"]);
3280
4500
  function baseBlock(sm) {
3281
4501
  return sm.blocks.get(conditionKey(BASE_CONDITION));
3282
4502
  }
3283
- var hasDangerousHtml3 = (node) => asElement4(node)?.meta.hasDangerousHtml ?? false;
3284
4503
  function withSizeShorthand(sm, value, important) {
3285
4504
  const baseKey = conditionKey(BASE_CONDITION);
3286
4505
  const blocks = /* @__PURE__ */ new Map();
@@ -3299,7 +4518,7 @@ function withSizeShorthand(sm, value, important) {
3299
4518
  }
3300
4519
  return { blocks };
3301
4520
  }
3302
- var sizeShorthand = pattern({
4521
+ var sizeShorthand = definePattern({
3303
4522
  name: "size-shorthand",
3304
4523
  category: "compress/size-shorthand",
3305
4524
  safety: 2,
@@ -3308,17 +4527,7 @@ var sizeShorthand = pattern({
3308
4527
  summary: "An element whose computed width and height are equal is rewritten to the single Tailwind size-* utility (size-* === width + height at the same value).",
3309
4528
  before: '<div style="width:1rem;height:1rem"/>',
3310
4529
  after: '<div class="size-4"/>',
3311
- safetyRationale: "size-* is value-identical to equal width+height; the element carries no ref/handlers/dynamic children/dangerous HTML, no dynamic class segment, and is not a combinator subject."
3312
- },
3313
- match: {
3314
- where: [
3315
- not(hasRef),
3316
- not(hasEventHandlers),
3317
- not(hasDynamicChildren),
3318
- not(hasDangerousHtml3),
3319
- not(hasDynamicClasses),
3320
- not(targetedByCombinator)
3321
- ]
4530
+ safetyRationale: "`size-*` is value-identical to equal width+height \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
3322
4531
  },
3323
4532
  rewrite: {
3324
4533
  rewriteClasses(computed2) {
@@ -3327,43 +4536,91 @@ var sizeShorthand = pattern({
3327
4536
  const h2 = base?.decls.get(HEIGHT);
3328
4537
  if (!w2 || !h2) return null;
3329
4538
  if (w2.important !== h2.important) return null;
3330
- if (NON_COLLAPSIBLE_VALUES.has(String(w2.value))) return null;
4539
+ if (NON_COLLAPSIBLE_VALUES5.has(String(w2.value))) return null;
3331
4540
  if (w2.value !== h2.value) return null;
3332
4541
  return withSizeShorthand(computed2, String(w2.value), w2.important);
3333
4542
  }
3334
4543
  },
3335
- examples: [
3336
- {
3337
- // Equal width/height collapse to a `size` decl at the IR level; the minimizing reverse-emit
3338
- // expands `size` back to width+height, finds the single utility covering both (`size-10`), and
3339
- // replaces the `h-10`+`w-10` pair with it. `bg-red-200` is preserved.
3340
- before: '<div className="h-10 w-10 bg-red-200">box</div>',
3341
- after: '<div className="bg-red-200 size-10">box</div>'
3342
- },
3343
- {
3344
- // Width and height differ → no equal-axis collapse.
3345
- noMatch: '<div className="h-10 w-20 bg-red-200">box</div>'
3346
- }
3347
- ]
4544
+ test: {
4545
+ cases: [
4546
+ {
4547
+ // Equal width/height collapse to a `size` decl at the IR level; the minimizing reverse-emit
4548
+ // expands `size` back to width+height, finds the single utility covering both (`size-10`), and
4549
+ // replaces the `h-10`+`w-10` pair with it. `bg-red-200` is preserved.
4550
+ before: '<div className="h-10 w-10 bg-red-200">box</div>',
4551
+ after: '<div className="bg-red-200 size-10">box</div>'
4552
+ }
4553
+ ],
4554
+ // Width and height differ → no equal-axis collapse.
4555
+ noMatch: ['<div className="h-10 w-20 bg-red-200">box</div>']
4556
+ }
3348
4557
  });
3349
4558
 
3350
4559
  // ../patterns/src/_registry.generated.ts
3351
4560
  var builtinPatterns = [
4561
+ displayContentsWrapper,
3352
4562
  emptyStyleDiv,
3353
4563
  flexCenterWrapper,
4564
+ inlineFlexCenterWrapper,
3354
4565
  nestedFlexMerge,
4566
+ nestedGridMerge,
3355
4567
  passthroughWrapper,
3356
4568
  redundantFragment,
4569
+ redundantInlineWrapper,
4570
+ borderRadiusShorthand,
4571
+ borderShorthand,
3357
4572
  dedupeClasses,
4573
+ gapShorthand,
3358
4574
  insetShorthand,
3359
4575
  marginShorthand,
4576
+ overflowShorthand,
4577
+ overscrollBehaviorShorthand,
3360
4578
  paddingShorthand,
4579
+ placeShorthand,
4580
+ scrollMarginShorthand,
4581
+ scrollPaddingShorthand,
3361
4582
  sizeShorthand
3362
4583
  ];
3363
4584
 
3364
4585
  // ../resolver-css/src/index.ts
3365
4586
  init_cjs_shims();
3366
- var import_node_fs = require("fs");
4587
+
4588
+ // ../resolver-css/src/constants.ts
4589
+ init_cjs_shims();
4590
+ var CSS_RESOLVER_ID = "css";
4591
+ var CSS_RESOLVER_PROVIDER = "custom-css";
4592
+ var ENGINE_VERSION = "css-index@1";
4593
+ var STRUCTURAL_PSEUDOS = /* @__PURE__ */ new Set([
4594
+ ":nth-child",
4595
+ ":nth-last-child",
4596
+ ":first-child",
4597
+ ":last-child",
4598
+ ":only-child",
4599
+ ":nth-of-type",
4600
+ ":nth-last-of-type",
4601
+ ":first-of-type",
4602
+ ":last-of-type",
4603
+ ":only-of-type"
4604
+ ]);
4605
+ var FUNCTIONAL_PSEUDOS = /* @__PURE__ */ new Set([
4606
+ ":not",
4607
+ ":is",
4608
+ ":where",
4609
+ ":has",
4610
+ ":matches"
4611
+ ]);
4612
+ var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set([
4613
+ ":before",
4614
+ ":after",
4615
+ ":first-line",
4616
+ ":first-letter"
4617
+ ]);
4618
+
4619
+ // ../resolver-css/src/resolver.ts
4620
+ init_cjs_shims();
4621
+
4622
+ // ../resolver-css/src/engine.ts
4623
+ init_cjs_shims();
3367
4624
  var import_node_module = require("module");
3368
4625
  var path2 = __toESM(require("path"), 1);
3369
4626
  function moduleBase() {
@@ -3401,34 +4658,82 @@ function ensurePostcss(projectRoot) {
3401
4658
  pc = engine.parse;
3402
4659
  sp = engine.selectorParser;
3403
4660
  }
3404
- var CSS_RESOLVER_ID = "css";
3405
- var CSS_RESOLVER_PROVIDER = "custom-css";
3406
- var ENGINE_VERSION = "css-index@1";
3407
- var STRUCTURAL_PSEUDOS = /* @__PURE__ */ new Set([
3408
- ":nth-child",
3409
- ":nth-last-child",
3410
- ":first-child",
3411
- ":last-child",
3412
- ":only-child",
3413
- ":nth-of-type",
3414
- ":nth-last-of-type",
3415
- ":first-of-type",
3416
- ":last-of-type",
3417
- ":only-of-type"
3418
- ]);
3419
- var FUNCTIONAL_PSEUDOS = /* @__PURE__ */ new Set([
3420
- ":not",
3421
- ":is",
3422
- ":where",
3423
- ":has",
3424
- ":matches"
3425
- ]);
3426
- var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set([
3427
- ":before",
3428
- ":after",
3429
- ":first-line",
3430
- ":first-letter"
3431
- ]);
4661
+
4662
+ // ../resolver-css/src/postcss-helpers.ts
4663
+ init_cjs_shims();
4664
+ function mediaContext(rule) {
4665
+ const parts = [];
4666
+ let skip = false;
4667
+ let parent = rule.parent;
4668
+ while (parent && parent.type === "atrule") {
4669
+ const at = parent;
4670
+ const name = at.name.toLowerCase();
4671
+ if (name === "media") parts.unshift(at.params.trim().replace(/\s+/g, " "));
4672
+ else if (name === "keyframes" || name.endsWith("keyframes") || name === "font-face") skip = true;
4673
+ parent = parent.parent;
4674
+ }
4675
+ return { media: parts.join(" and "), skip };
4676
+ }
4677
+ function collectDecls(rule) {
4678
+ const out = [];
4679
+ for (const node of rule.nodes) {
4680
+ if (node.type === "decl") out.push([node.prop, node.value, node.important === true]);
4681
+ }
4682
+ return out;
4683
+ }
4684
+
4685
+ // ../resolver-css/src/misc-helpers.ts
4686
+ init_cjs_shims();
4687
+ var import_node_fs = require("fs");
4688
+ function isPlainClassToken(token) {
4689
+ return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
4690
+ }
4691
+ function readCssPath(path7) {
4692
+ try {
4693
+ return { id: path7, css: (0, import_node_fs.readFileSync)(path7, "utf8") };
4694
+ } catch (cause) {
4695
+ throw new Error(`resolver-css: cannot read CSS file "${path7}"`, { cause });
4696
+ }
4697
+ }
4698
+ function deriveFingerprint(provider, files) {
4699
+ const parts = files.map((f) => `${f.id}:${f.css.length}`).sort();
4700
+ return `${provider}/${ENGINE_VERSION}::${parts.join("|")}`;
4701
+ }
4702
+
4703
+ // ../resolver-css/src/selector-helpers.ts
4704
+ init_cjs_shims();
4705
+ function splitCompounds(selector) {
4706
+ const compounds = [];
4707
+ let current = [];
4708
+ let leftCombinator = null;
4709
+ for (const node of selector.nodes) {
4710
+ if (sp.isCombinator(node)) {
4711
+ compounds.push({ leftCombinator, nodes: current });
4712
+ current = [];
4713
+ leftCombinator = combinatorValue(node);
4714
+ } else {
4715
+ current.push(node);
4716
+ }
4717
+ }
4718
+ compounds.push({ leftCombinator, nodes: current });
4719
+ return compounds;
4720
+ }
4721
+ function combinatorValue(node) {
4722
+ const v2 = node.value;
4723
+ return v2.trim() === "" ? " " : v2.trim();
4724
+ }
4725
+ function pseudoName(node) {
4726
+ return node.value.toLowerCase();
4727
+ }
4728
+ function isPseudoElement(node) {
4729
+ return sp.isPseudoElement(node) || LEGACY_PSEUDO_ELEMENTS.has(pseudoName(node));
4730
+ }
4731
+ function normalizePseudoElement(node) {
4732
+ const name = pseudoName(node);
4733
+ return name.startsWith("::") ? name : `::${name.replace(/^:/, "")}`;
4734
+ }
4735
+
4736
+ // ../resolver-css/src/resolver.ts
3432
4737
  var CustomCSSResolver = class {
3433
4738
  id = CSS_RESOLVER_ID;
3434
4739
  provider = CSS_RESOLVER_PROVIDER;
@@ -3478,7 +4783,7 @@ var CustomCSSResolver = class {
3478
4783
  const remaining = /* @__PURE__ */ new Map();
3479
4784
  for (const [ck, block] of norm.normalizeStyleMap(styles).blocks) {
3480
4785
  for (const [prop, decl] of block.decls) {
3481
- remaining.set(`${ck}\0${prop}`, String(decl.value));
4786
+ remaining.set(`${ck} ${prop}`, String(decl.value));
3482
4787
  }
3483
4788
  }
3484
4789
  if (remaining.size === 0) return { classes: [], exact: true, warnings: [] };
@@ -3498,6 +4803,15 @@ var CustomCSSResolver = class {
3498
4803
  }
3499
4804
  return { classes, exact: remaining.size === 0, warnings: [] };
3500
4805
  }
4806
+ /**
4807
+ * Return a CSS stylesheet defining the given class tokens, so a verifier can render a subtree with
4808
+ * the project's real styling applied. The source stylesheets ARE the definition, so we hand back
4809
+ * their concatenation verbatim (every relevant rule — including combinator/structural selectors —
4810
+ * is preserved). `classes` is accepted for interface parity but the full source is always returned.
4811
+ */
4812
+ cssFor(_classes) {
4813
+ return this.#files.map((f) => f.css).join("\n");
4814
+ }
3501
4815
  selectorUsage(token) {
3502
4816
  const u2 = this.#usage.get(token);
3503
4817
  if (!u2) {
@@ -3682,87 +4996,79 @@ var CustomCSSResolver = class {
3682
4996
  if (this.#reverse) return this.#reverse;
3683
4997
  const out = [];
3684
4998
  for (const token of this.#classIndex.keys()) {
3685
- const styles = this.#resolveTokens([token], [token]);
3686
- const keyed = /* @__PURE__ */ new Map();
3687
- for (const [ck, block] of styles.blocks) {
3688
- for (const [prop, decl] of block.decls) keyed.set(`${ck}\0${prop}`, String(decl.value));
3689
- }
3690
- if (keyed.size > 0) out.push({ token, keyed });
3691
- }
3692
- out.sort((a, b3) => b3.keyed.size - a.keyed.size);
3693
- this.#reverse = out;
3694
- return out;
3695
- }
3696
- };
3697
- function createCssResolver(cssFiles = [], options) {
3698
- return new CustomCSSResolver(cssFiles, options);
3699
- }
3700
- function splitCompounds(selector) {
3701
- const compounds = [];
3702
- let current = [];
3703
- let leftCombinator = null;
3704
- for (const node of selector.nodes) {
3705
- if (sp.isCombinator(node)) {
3706
- compounds.push({ leftCombinator, nodes: current });
3707
- current = [];
3708
- leftCombinator = combinatorValue(node);
3709
- } else {
3710
- current.push(node);
4999
+ const styles = this.#resolveTokens([token], [token]);
5000
+ const keyed = /* @__PURE__ */ new Map();
5001
+ for (const [ck, block] of styles.blocks) {
5002
+ for (const [prop, decl] of block.decls) keyed.set(`${ck} ${prop}`, String(decl.value));
5003
+ }
5004
+ if (keyed.size > 0) out.push({ token, keyed });
3711
5005
  }
5006
+ out.sort((a, b3) => b3.keyed.size - a.keyed.size);
5007
+ this.#reverse = out;
5008
+ return out;
3712
5009
  }
3713
- compounds.push({ leftCombinator, nodes: current });
3714
- return compounds;
3715
- }
3716
- function combinatorValue(node) {
3717
- const v2 = node.value;
3718
- return v2.trim() === "" ? " " : v2.trim();
3719
- }
3720
- function pseudoName(node) {
3721
- return node.value.toLowerCase();
3722
- }
3723
- function isPseudoElement(node) {
3724
- return sp.isPseudoElement(node) || LEGACY_PSEUDO_ELEMENTS.has(pseudoName(node));
3725
- }
3726
- function normalizePseudoElement(node) {
3727
- const name = pseudoName(node);
3728
- return name.startsWith("::") ? name : `::${name.replace(/^:/, "")}`;
5010
+ };
5011
+ function createCssResolver(cssFiles = [], options) {
5012
+ return new CustomCSSResolver(cssFiles, options);
3729
5013
  }
3730
- function mediaContext(rule) {
3731
- const parts = [];
3732
- let skip = false;
3733
- let parent = rule.parent;
3734
- while (parent && parent.type === "atrule") {
3735
- const at = parent;
3736
- const name = at.name.toLowerCase();
3737
- if (name === "media") parts.unshift(at.params.trim().replace(/\s+/g, " "));
3738
- else if (name === "keyframes" || name.endsWith("keyframes") || name === "font-face") skip = true;
3739
- parent = parent.parent;
5014
+
5015
+ // ../resolver-tailwind/src/index.ts
5016
+ init_cjs_shims();
5017
+
5018
+ // ../resolver-tailwind/src/tailwind/resolver.ts
5019
+ init_cjs_shims();
5020
+
5021
+ // ../resolver-tailwind/src/tailwind/emit.ts
5022
+ init_cjs_shims();
5023
+
5024
+ // ../resolver-tailwind/src/tailwind/fingerprint.ts
5025
+ init_cjs_shims();
5026
+ function fnv1a(input) {
5027
+ let h2 = 2166136261;
5028
+ for (let i = 0; i < input.length; i += 1) {
5029
+ h2 ^= input.charCodeAt(i);
5030
+ h2 = Math.imul(h2, 16777619);
3740
5031
  }
3741
- return { media: parts.join(" and "), skip };
5032
+ return (h2 >>> 0).toString(16).padStart(8, "0");
3742
5033
  }
3743
- function collectDecls(rule) {
3744
- const out = [];
3745
- for (const node of rule.nodes) {
3746
- if (node.type === "decl") out.push([node.prop, node.value, node.important === true]);
5034
+
5035
+ // ../resolver-tailwind/src/tailwind/emit.ts
5036
+ function expandForEmit(norm, prop, value, important) {
5037
+ const pairsFor = (p2, v2) => norm.normalizeDeclaration(p2, v2, important).map((d3) => [d3.property, String(d3.value)]);
5038
+ if (prop === "size") {
5039
+ return [...pairsFor("width", value), ...pairsFor("height", value)];
3747
5040
  }
3748
- return out;
3749
- }
3750
- function isPlainClassToken(token) {
3751
- return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
5041
+ if (prop === "inset-block" || prop === "inset-inline") {
5042
+ const parts = value.split(/\s+/).filter((s) => s.length > 0);
5043
+ const a = parts[0] ?? value;
5044
+ const b3 = parts[1] ?? a;
5045
+ const sides = prop === "inset-block" ? ["top", "bottom"] : ["left", "right"];
5046
+ return [...pairsFor(sides[0], a), ...pairsFor(sides[1], b3)];
5047
+ }
5048
+ return pairsFor(prop, value);
3752
5049
  }
3753
- function readCssPath(path6) {
5050
+ function synthesizeResidual(remaining, ctx) {
5051
+ if (remaining.size === 0) return void 0;
5052
+ const norm = ctx.normalizer ?? normalizer;
5053
+ const decls = /* @__PURE__ */ new Map();
5054
+ for (const [prop, value] of remaining) {
5055
+ for (const decl of norm.normalizeDeclaration(String(prop), value, false)) {
5056
+ decls.set(decl.property, decl);
5057
+ }
5058
+ }
5059
+ const block = { condition: BASE_CONDITION, decls };
5060
+ const styleMap = { blocks: /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]) };
5061
+ const css = [...remaining].map(([p2, v2]) => `${p2}:${v2}`).join(";");
5062
+ const className = `df-${fnv1a(css)}`;
5063
+ const synthetic = { className, decls: styleMap, css: `.${className}{${css}}` };
3754
5064
  try {
3755
- return { id: path6, css: (0, import_node_fs.readFileSync)(path6, "utf8") };
3756
- } catch (cause) {
3757
- throw new Error(`resolver-css: cannot read CSS file "${path6}"`, { cause });
5065
+ ctx.sink.register(synthetic);
5066
+ } catch {
3758
5067
  }
3759
- }
3760
- function deriveFingerprint(provider, files) {
3761
- const parts = files.map((f) => `${f.id}:${f.css.length}`).sort();
3762
- return `${provider}/${ENGINE_VERSION}::${parts.join("|")}`;
5068
+ return synthetic;
3763
5069
  }
3764
5070
 
3765
- // ../resolver-tailwind/src/index.ts
5071
+ // ../resolver-tailwind/src/tailwind/engine.ts
3766
5072
  init_cjs_shims();
3767
5073
  var import_node_module2 = require("module");
3768
5074
  var path3 = __toESM(require("path"), 1);
@@ -3811,6 +5117,12 @@ function loadEngine(options) {
3811
5117
  return null;
3812
5118
  }
3813
5119
  }
5120
+
5121
+ // ../resolver-tailwind/src/tailwind/extract.ts
5122
+ init_cjs_shims();
5123
+
5124
+ // ../resolver-tailwind/src/tailwind/selector.ts
5125
+ init_cjs_shims();
3814
5126
  var LEGACY_PSEUDO_ELEMENTS2 = /* @__PURE__ */ new Set([
3815
5127
  ":before",
3816
5128
  ":after",
@@ -3857,6 +5169,26 @@ function makeCondition(media, states, pseudoElement) {
3857
5169
  pseudoElement
3858
5170
  };
3859
5171
  }
5172
+ function unescapeClass(selector) {
5173
+ const sel = selector.trim();
5174
+ if (sel[0] !== ".") return null;
5175
+ let out = "";
5176
+ for (let i = 1; i < sel.length; i += 1) {
5177
+ const c = sel[i];
5178
+ if (c === "\\") {
5179
+ i += 1;
5180
+ if (i < sel.length) out += sel[i];
5181
+ continue;
5182
+ }
5183
+ if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
5184
+ return null;
5185
+ }
5186
+ out += c;
5187
+ }
5188
+ return out.length > 0 ? out : null;
5189
+ }
5190
+
5191
+ // ../resolver-tailwind/src/tailwind/extract.ts
3860
5192
  function collectRules(node, mediaStack, inUnsupportedAtRule, out) {
3861
5193
  if (node.type === "rule") {
3862
5194
  out.push({ rule: node, media: mediaStack, unsupported: inUnsupportedAtRule });
@@ -3896,9 +5228,33 @@ function extractToken(token, nodes) {
3896
5228
  const mediaQuery = media.join(" and ");
3897
5229
  blocks.push({ condition: makeCondition(mediaQuery, parsed.states, parsed.pseudoElement), decls });
3898
5230
  }
3899
- const opaque = sawComplex && blocks.length === 0 ? { token, reason: "combinator-variant", detail: "utility targets descendants/siblings, not its own box" } : void 0;
3900
- return { blocks, produced: true, opaque };
5231
+ const opaque2 = sawComplex && blocks.length === 0 ? { token, reason: "combinator-variant", detail: "utility targets descendants/siblings, not its own box" } : void 0;
5232
+ return { blocks, produced: true, opaque: opaque2 };
5233
+ }
5234
+
5235
+ // ../resolver-tailwind/src/tailwind/serialize.ts
5236
+ init_cjs_shims();
5237
+ function serializeCssNode(node) {
5238
+ if (node.type === "decl") {
5239
+ const d3 = node;
5240
+ if (typeof d3.value !== "string") return "";
5241
+ return `${d3.prop}:${d3.value}${d3.important === true ? " !important" : ""}`;
5242
+ }
5243
+ if (node.type === "rule") {
5244
+ const r2 = node;
5245
+ const body = (r2.nodes ?? []).map((c) => serializeCssNode(c)).filter((s) => s.length > 0).join(";");
5246
+ return `${r2.selector}{${body}}`;
5247
+ }
5248
+ if (node.type === "atrule") {
5249
+ const a = node;
5250
+ const body = (a.nodes ?? []).map((c) => serializeCssNode(c)).filter((s) => s.length > 0).join("");
5251
+ return `@${a.name} ${a.params}{${body}}`;
5252
+ }
5253
+ return "";
3901
5254
  }
5255
+
5256
+ // ../resolver-tailwind/src/tailwind/stylemap.ts
5257
+ init_cjs_shims();
3902
5258
  function buildStyleMap(blockMaps) {
3903
5259
  if (blockMaps.size === 0) return emptyStyleMap();
3904
5260
  const blocks = /* @__PURE__ */ new Map();
@@ -3921,6 +5277,9 @@ function shadowedBy(prev) {
3921
5277
  add(prev.origin);
3922
5278
  return out.length > 0 ? out : void 0;
3923
5279
  }
5280
+
5281
+ // ../resolver-tailwind/src/tailwind/usage.ts
5282
+ init_cjs_shims();
3924
5283
  var OPAQUE_USAGE = {
3925
5284
  asSubject: true,
3926
5285
  asAncestor: true,
@@ -3939,14 +5298,8 @@ var DROPPABLE_USAGE = {
3939
5298
  asStructural: false,
3940
5299
  droppable: true
3941
5300
  };
3942
- function fnv1a(input) {
3943
- let h2 = 2166136261;
3944
- for (let i = 0; i < input.length; i += 1) {
3945
- h2 ^= input.charCodeAt(i);
3946
- h2 = Math.imul(h2, 16777619);
3947
- }
3948
- return (h2 >>> 0).toString(16).padStart(8, "0");
3949
- }
5301
+
5302
+ // ../resolver-tailwind/src/tailwind/resolver.ts
3950
5303
  var TailwindResolver = class {
3951
5304
  id = "tailwind";
3952
5305
  provider;
@@ -3992,14 +5345,14 @@ var TailwindResolver = class {
3992
5345
  const blockMaps = /* @__PURE__ */ new Map();
3993
5346
  const resolved = [];
3994
5347
  const unknown = [];
3995
- const opaque = [];
5348
+ const opaque2 = [];
3996
5349
  input.classes.forEach((token, tokenIndex) => {
3997
5350
  const extracted = this.#extract(token);
3998
5351
  if (!extracted.produced) {
3999
5352
  unknown.push(token);
4000
5353
  return;
4001
5354
  }
4002
- if (extracted.opaque) opaque.push(extracted.opaque);
5355
+ if (extracted.opaque) opaque2.push(extracted.opaque);
4003
5356
  if (extracted.blocks.length === 0) return;
4004
5357
  const origin = { kind: "class", tokenIndex, className: token };
4005
5358
  let contributed = false;
@@ -4025,7 +5378,7 @@ var TailwindResolver = class {
4025
5378
  styles: buildStyleMap(blockMaps),
4026
5379
  resolved,
4027
5380
  unknown,
4028
- opaque,
5381
+ opaque: opaque2,
4029
5382
  warnings: []
4030
5383
  };
4031
5384
  this.#resolveCache.set(key, result);
@@ -4121,6 +5474,21 @@ var TailwindResolver = class {
4121
5474
  const residual = synthesizeResidual(remaining, ctx);
4122
5475
  return residual ? { classes, residual, exact, warnings: [] } : { classes, exact, warnings: [] };
4123
5476
  }
5477
+ /**
5478
+ * Generate a CSS stylesheet that defines `classes`, so a verifier can render a subtree with the
5479
+ * real Tailwind styling applied. Backed by the same engine `resolve` uses (`generate(candidates)`),
5480
+ * serialized to plain CSS. Returns `''` when the engine is unavailable or generates nothing.
5481
+ */
5482
+ cssFor(classes) {
5483
+ if (!this.#engine) return "";
5484
+ const tokens = [...new Set(classes)].filter((c) => c.length > 0);
5485
+ if (tokens.length === 0) return "";
5486
+ try {
5487
+ return this.#engine.generate(tokens).map((n) => serializeCssNode(n)).filter((s) => s.length > 0).join("\n");
5488
+ } catch {
5489
+ return "";
5490
+ }
5491
+ }
4124
5492
  selectorUsage(token) {
4125
5493
  const ex = this.#extract(token);
4126
5494
  if (!ex.produced || ex.opaque) return OPAQUE_USAGE;
@@ -4129,58 +5497,6 @@ var TailwindResolver = class {
4129
5497
  return DROPPABLE_USAGE;
4130
5498
  }
4131
5499
  };
4132
- function expandForEmit(norm, prop, value, important) {
4133
- const pairsFor = (p2, v2) => norm.normalizeDeclaration(p2, v2, important).map((d3) => [d3.property, String(d3.value)]);
4134
- if (prop === "size") {
4135
- return [...pairsFor("width", value), ...pairsFor("height", value)];
4136
- }
4137
- if (prop === "inset-block" || prop === "inset-inline") {
4138
- const parts = value.split(/\s+/).filter((s) => s.length > 0);
4139
- const a = parts[0] ?? value;
4140
- const b3 = parts[1] ?? a;
4141
- const sides = prop === "inset-block" ? ["top", "bottom"] : ["left", "right"];
4142
- return [...pairsFor(sides[0], a), ...pairsFor(sides[1], b3)];
4143
- }
4144
- return pairsFor(prop, value);
4145
- }
4146
- function synthesizeResidual(remaining, ctx) {
4147
- if (remaining.size === 0) return void 0;
4148
- const norm = ctx.normalizer ?? normalizer;
4149
- const decls = /* @__PURE__ */ new Map();
4150
- for (const [prop, value] of remaining) {
4151
- for (const decl of norm.normalizeDeclaration(String(prop), value, false)) {
4152
- decls.set(decl.property, decl);
4153
- }
4154
- }
4155
- const block = { condition: BASE_CONDITION, decls };
4156
- const styleMap = { blocks: /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]) };
4157
- const css = [...remaining].map(([p2, v2]) => `${p2}:${v2}`).join(";");
4158
- const className = `df-${fnv1a(css)}`;
4159
- const synthetic = { className, decls: styleMap, css: `.${className}{${css}}` };
4160
- try {
4161
- ctx.sink.register(synthetic);
4162
- } catch {
4163
- }
4164
- return synthetic;
4165
- }
4166
- function unescapeClass(selector) {
4167
- const sel = selector.trim();
4168
- if (sel[0] !== ".") return null;
4169
- let out = "";
4170
- for (let i = 1; i < sel.length; i += 1) {
4171
- const c = sel[i];
4172
- if (c === "\\") {
4173
- i += 1;
4174
- if (i < sel.length) out += sel[i];
4175
- continue;
4176
- }
4177
- if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
4178
- return null;
4179
- }
4180
- out += c;
4181
- }
4182
- return out.length > 0 ? out : null;
4183
- }
4184
5500
  function createTailwindResolver(config) {
4185
5501
  return new TailwindResolver(config);
4186
5502
  }
@@ -4255,66 +5571,68 @@ function createTransform(options) {
4255
5571
  const projectRoot = options.projectRoot ?? process.cwd();
4256
5572
  const resolver = buildResolver(options.provider, options.css, projectRoot);
4257
5573
  const patterns = selectPatterns(options.passes);
5574
+ function prepare(code, id, kind, gate) {
5575
+ const parsed = createJsxFrontend().parse(code, {
5576
+ id,
5577
+ kind,
5578
+ resolver,
5579
+ normalizer,
5580
+ config: {},
5581
+ onDiagnostic: () => {
5582
+ }
5583
+ });
5584
+ const doc = parsed.doc;
5585
+ const nodesIn = doc.nodes.size;
5586
+ for (const node of doc.nodes.values()) node.meta.safetyFloor = 3;
5587
+ const ctx = {
5588
+ doc,
5589
+ safetyCeiling: options.safety,
5590
+ normalizer,
5591
+ // Real CSS-selector-safety index from the active resolver (custom-CSS reports combinator /
5592
+ // structural-pseudo coupling; Tailwind has none → null index, behaviour unchanged).
5593
+ selectors: buildSelectorIndex(doc, resolver),
5594
+ resolver,
5595
+ gate
5596
+ };
5597
+ return { doc, ctx, passes: buildPasses(patterns), nodesIn };
5598
+ }
5599
+ function finish(code, optimized, id, nodesIn) {
5600
+ syncClassesFromComputed(optimized, resolver, normalizer);
5601
+ const printed = createJsxBackend().print(
5602
+ optimized,
5603
+ { moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
5604
+ { normalizer, resolver, sink: createSyntheticSink(), eol: "\n", onDiagnostic: () => {
5605
+ } }
5606
+ );
5607
+ const out = printed.code;
5608
+ const nodesOut = optimized.nodes.size;
5609
+ const classesBefore = countClassTokens(code);
5610
+ const classesAfter = countClassTokens(out);
5611
+ return {
5612
+ code: out,
5613
+ changed: out !== code,
5614
+ passthrough: false,
5615
+ stats: {
5616
+ nodesIn,
5617
+ nodesOut,
5618
+ nodesRemoved: Math.max(0, nodesIn - nodesOut),
5619
+ classesBefore,
5620
+ classesAfter,
5621
+ classesSaved: Math.max(0, classesBefore - classesAfter),
5622
+ bytesBefore: bytes(code),
5623
+ bytesAfter: bytes(out),
5624
+ bytesSaved: bytes(code) - bytes(out)
5625
+ }
5626
+ };
5627
+ }
4258
5628
  return {
4259
5629
  resolver,
4260
5630
  transformFile(code, id) {
4261
5631
  const kind = jsxKindOf(id);
4262
5632
  if (kind === null) return passthroughResult(code);
4263
- const parsed = createJsxFrontend().parse(code, {
4264
- id,
4265
- kind,
4266
- resolver,
4267
- normalizer,
4268
- config: {},
4269
- onDiagnostic: () => {
4270
- }
4271
- });
4272
- const doc = parsed.doc;
4273
- const nodesIn = doc.nodes.size;
4274
- for (const node of doc.nodes.values()) node.meta.safetyFloor = 3;
4275
- const ctx = {
4276
- doc,
4277
- safetyCeiling: options.safety,
4278
- normalizer,
4279
- // Real CSS-selector-safety index from the active resolver (custom-CSS reports combinator /
4280
- // structural-pseudo coupling; Tailwind has none → null index, behaviour unchanged).
4281
- selectors: buildSelectorIndex(doc, resolver),
4282
- resolver
4283
- };
4284
- const { doc: optimized } = runPasses(doc, buildPasses(patterns), ctx);
4285
- syncClassesFromComputed(optimized, resolver, normalizer);
4286
- const printed = createJsxBackend().print(
4287
- optimized,
4288
- { moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
4289
- {
4290
- normalizer,
4291
- resolver,
4292
- sink: createSyntheticSink(),
4293
- eol: "\n",
4294
- onDiagnostic: () => {
4295
- }
4296
- }
4297
- );
4298
- const out = printed.code;
4299
- const nodesOut = optimized.nodes.size;
4300
- const classesBefore = countClassTokens(code);
4301
- const classesAfter = countClassTokens(out);
4302
- return {
4303
- code: out,
4304
- changed: out !== code,
4305
- passthrough: false,
4306
- stats: {
4307
- nodesIn,
4308
- nodesOut,
4309
- nodesRemoved: Math.max(0, nodesIn - nodesOut),
4310
- classesBefore,
4311
- classesAfter,
4312
- classesSaved: Math.max(0, classesBefore - classesAfter),
4313
- bytesBefore: bytes(code),
4314
- bytesAfter: bytes(out),
4315
- bytesSaved: bytes(code) - bytes(out)
4316
- }
4317
- };
5633
+ const { doc, ctx, passes, nodesIn } = prepare(code, id, kind, "provably-safe");
5634
+ const { doc: optimized } = runPasses(doc, passes, ctx);
5635
+ return finish(code, optimized, id, nodesIn);
4318
5636
  }
4319
5637
  };
4320
5638
  }
@@ -4995,19 +6313,112 @@ ${import_picocolors2.default.gray(d2)} ${t}
4995
6313
  };
4996
6314
  var J2 = `${import_picocolors2.default.gray(o)} `;
4997
6315
 
6316
+ // ../cli/src/detect.ts
6317
+ init_cjs_shims();
6318
+ var fs2 = __toESM(require("fs"), 1);
6319
+ var path5 = __toESM(require("path"), 1);
6320
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
6321
+ "node_modules",
6322
+ "dist",
6323
+ "build",
6324
+ ".next",
6325
+ "out",
6326
+ "coverage",
6327
+ ".git",
6328
+ "domflax-out"
6329
+ ]);
6330
+ var COMMON_INPUT_DIRS = ["src", "app", "components", "pages", "lib", "ui", "public"];
6331
+ var CSS_FILE_CAP = 200;
6332
+ function toRelative(root, abs) {
6333
+ const rel = path5.relative(root, abs);
6334
+ return rel.split(path5.sep).join("/");
6335
+ }
6336
+ function detectCssFiles(root) {
6337
+ const found = [];
6338
+ let capped = false;
6339
+ const walk = (dir) => {
6340
+ if (capped) return;
6341
+ let entries;
6342
+ try {
6343
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
6344
+ } catch {
6345
+ return;
6346
+ }
6347
+ for (const entry of entries) {
6348
+ const full = path5.join(dir, entry.name);
6349
+ if (entry.isDirectory()) {
6350
+ if (SKIP_DIRS2.has(entry.name)) continue;
6351
+ walk(full);
6352
+ if (capped) return;
6353
+ } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".css")) {
6354
+ found.push(toRelative(root, full));
6355
+ if (found.length >= CSS_FILE_CAP) {
6356
+ capped = true;
6357
+ return;
6358
+ }
6359
+ }
6360
+ }
6361
+ };
6362
+ walk(path5.resolve(root));
6363
+ found.sort((a, b3) => a.localeCompare(b3));
6364
+ if (capped) {
6365
+ console.error(`domflax: more than ${CSS_FILE_CAP} CSS files found; showing the first ${CSS_FILE_CAP}.`);
6366
+ }
6367
+ return found;
6368
+ }
6369
+ function detectInputDirs(root) {
6370
+ const resolved = path5.resolve(root);
6371
+ return COMMON_INPUT_DIRS.filter((name) => {
6372
+ try {
6373
+ return fs2.statSync(path5.join(resolved, name)).isDirectory();
6374
+ } catch {
6375
+ return false;
6376
+ }
6377
+ });
6378
+ }
6379
+
4998
6380
  // ../cli/src/wizard.ts
6381
+ var OTHER_INPUT = "\0domflax.other";
4999
6382
  var WIZARD_CANCELLED = /* @__PURE__ */ Symbol("domflax.wizard.cancelled");
5000
6383
  function cancelled(value) {
5001
6384
  return pD(value);
5002
6385
  }
5003
6386
  async function runWizard(base) {
5004
6387
  Ie("domflax \u2014 optimize your markup");
5005
- const pathInput = await he({
5006
- message: "Which folder, glob, or file should domflax optimize?",
5007
- placeholder: "src",
5008
- defaultValue: "src"
5009
- });
5010
- if (cancelled(pathInput)) return done();
6388
+ const root = base.projectRoot ?? process.cwd();
6389
+ const detectedInputs = detectInputDirs(root);
6390
+ let inputPath;
6391
+ if (detectedInputs.length > 0) {
6392
+ const defaultInput = detectedInputs.includes("src") ? "src" : detectedInputs[0];
6393
+ const choice = await ve({
6394
+ message: "Which folder should domflax optimize?",
6395
+ options: [
6396
+ ...detectedInputs.map((dir) => ({ value: dir, label: dir, hint: "detected" })),
6397
+ { value: OTHER_INPUT, label: "Other (type a path)\u2026" }
6398
+ ],
6399
+ initialValue: defaultInput
6400
+ });
6401
+ if (cancelled(choice)) return done();
6402
+ if (choice === OTHER_INPUT) {
6403
+ const typed = await he({
6404
+ message: "Which folder, glob, or file should domflax optimize?",
6405
+ placeholder: "src",
6406
+ defaultValue: "src"
6407
+ });
6408
+ if (cancelled(typed)) return done();
6409
+ inputPath = String(typed);
6410
+ } else {
6411
+ inputPath = String(choice);
6412
+ }
6413
+ } else {
6414
+ const typed = await he({
6415
+ message: "Which folder, glob, or file should domflax optimize?",
6416
+ placeholder: "src",
6417
+ defaultValue: "src"
6418
+ });
6419
+ if (cancelled(typed)) return done();
6420
+ inputPath = String(typed);
6421
+ }
5011
6422
  const outputMode = await ve({
5012
6423
  message: "Where should the optimized files go?",
5013
6424
  options: [
@@ -5056,17 +6467,29 @@ async function runWizard(base) {
5056
6467
  if (cancelled(provider)) return done();
5057
6468
  let css = base.css;
5058
6469
  if (provider === "custom") {
5059
- const cssInput = await he({
5060
- message: "CSS files (space-separated):",
5061
- placeholder: "src/styles.css"
5062
- });
5063
- if (cancelled(cssInput)) return done();
5064
- css = String(cssInput).split(/\s+/).filter((s) => s.length > 0);
6470
+ const detectedCss = detectCssFiles(root);
6471
+ if (detectedCss.length > 0) {
6472
+ const picked = await fe({
6473
+ message: "Which CSS files should resolve your classes? (all detected files are pre-selected)",
6474
+ options: detectedCss.map((file) => ({ value: file, label: file })),
6475
+ initialValues: [...detectedCss],
6476
+ required: false
6477
+ });
6478
+ if (cancelled(picked)) return done();
6479
+ css = picked;
6480
+ } else {
6481
+ const cssInput = await he({
6482
+ message: "CSS files (space-separated):",
6483
+ placeholder: "src/styles.css"
6484
+ });
6485
+ if (cancelled(cssInput)) return done();
6486
+ css = String(cssInput).split(/\s+/).filter((s) => s.length > 0);
6487
+ }
5065
6488
  }
5066
6489
  Se("Ready \u2014 running domflax.");
5067
6490
  return {
5068
6491
  ...base,
5069
- paths: [String(pathInput)],
6492
+ paths: [inputPath],
5070
6493
  out,
5071
6494
  provider,
5072
6495
  css,
@@ -5098,7 +6521,7 @@ function printReport(totals) {
5098
6521
  console.log(` classes saved : ${totals.classesSaved}`);
5099
6522
  console.log(` bytes saved : ${totals.bytesSaved}`);
5100
6523
  }
5101
- function execute(options) {
6524
+ async function execute(options) {
5102
6525
  const { files, inputRoot, warnings } = discoverInputs(options.paths);
5103
6526
  for (const w2 of warnings) console.error(`domflax: ${w2}`);
5104
6527
  if (files.length === 0) {
@@ -5128,7 +6551,7 @@ function execute(options) {
5128
6551
  const result = transform.transformFile(code, file);
5129
6552
  addStats(totals, result.stats, result.changed);
5130
6553
  if (options.dryRun) {
5131
- const rel = path5.relative(inputRoot, file) || path5.basename(file);
6554
+ const rel = path6.relative(inputRoot, file) || path6.basename(file);
5132
6555
  if (result.changed) console.log(unifiedDiff(code, result.code, rel));
5133
6556
  else if (!options.report) console.log(` (unchanged) ${rel}`);
5134
6557
  continue;
@@ -5141,9 +6564,9 @@ function execute(options) {
5141
6564
  continue;
5142
6565
  }
5143
6566
  try {
5144
- (0, import_node_fs2.mkdirSync)(path5.dirname(target.value), { recursive: true });
6567
+ (0, import_node_fs2.mkdirSync)(path6.dirname(target.value), { recursive: true });
5145
6568
  (0, import_node_fs2.writeFileSync)(target.value, result.code, "utf8");
5146
- console.log(`domflax: wrote ${path5.relative(process.cwd(), target.value) || target.value}`);
6569
+ console.log(`domflax: wrote ${path6.relative(process.cwd(), target.value) || target.value}`);
5147
6570
  } catch (err) {
5148
6571
  console.error(`domflax: cannot write ${target.value}: ${String(err?.message ?? err)}`);
5149
6572
  failures += 1;
@@ -5180,27 +6603,13 @@ async function main(argv = process.argv.slice(2)) {
5180
6603
  return;
5181
6604
  }
5182
6605
  try {
5183
- const result = execute(options);
6606
+ const result = await execute(options);
5184
6607
  process.exitCode = result.exitCode;
5185
6608
  } catch (err) {
5186
6609
  console.error(`domflax: ${err instanceof Error ? err.message : String(err)}`);
5187
6610
  process.exitCode = 1;
5188
6611
  }
5189
6612
  }
5190
- function isMainEntry() {
5191
- const entry = process.argv[1];
5192
- if (entry === void 0) return false;
5193
- try {
5194
- const self = path5.resolve((0, import_node_url.fileURLToPath)(importMetaUrl));
5195
- const argv = path5.resolve(entry);
5196
- return process.platform === "win32" ? self.toLowerCase() === argv.toLowerCase() : self === argv;
5197
- } catch {
5198
- return false;
5199
- }
5200
- }
5201
- if (isMainEntry()) {
5202
- void main();
5203
- }
5204
6613
 
5205
6614
  // src/cli.ts
5206
6615
  main(process.argv.slice(2));