domflax 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +25 -8
  2. package/dist/{chunk-DNHOGPYV.js → chunk-3Z5ZWLXX.js} +407 -51
  3. package/dist/chunk-3Z5ZWLXX.js.map +1 -0
  4. package/dist/{chunk-DOQEBGWB.js → chunk-5FWENSD2.js} +63 -8
  5. package/dist/chunk-5FWENSD2.js.map +1 -0
  6. package/dist/chunk-EVENAJYI.js +336 -0
  7. package/dist/chunk-EVENAJYI.js.map +1 -0
  8. package/dist/{chunk-DWLB7FRR.js → chunk-H5KTGI3A.js} +153 -7
  9. package/dist/chunk-H5KTGI3A.js.map +1 -0
  10. package/dist/{chunk-6WVVF6AD.js → chunk-U5GOONKV.js} +5 -2
  11. package/dist/{chunk-6WVVF6AD.js.map → chunk-U5GOONKV.js.map} +1 -1
  12. package/dist/cli.cjs +1033 -178
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js +285 -243
  15. package/dist/cli.js.map +1 -1
  16. package/dist/index.cjs +614 -68
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +34 -18
  19. package/dist/index.d.ts +34 -18
  20. package/dist/index.js +4 -4
  21. package/dist/{pattern-F5xBtIE-.d.cts → pattern-CP9_HpVK.d.cts} +1 -1
  22. package/dist/{pattern-CV607P87.d.ts → pattern-CYgsv-jO.d.ts} +1 -1
  23. package/dist/pattern-kit.cjs.map +1 -1
  24. package/dist/pattern-kit.d.cts +2 -2
  25. package/dist/pattern-kit.d.ts +2 -2
  26. package/dist/pattern-kit.js +2 -2
  27. package/dist/{resolve-ops-DIwEelH-.d.ts → resolve-ops-Ci7LgYHC.d.cts} +9 -0
  28. package/dist/{resolve-ops-DIwEelH-.d.cts → resolve-ops-Ci7LgYHC.d.ts} +9 -0
  29. package/dist/verify.d.cts +1 -1
  30. package/dist/verify.d.ts +1 -1
  31. package/dist/verify.js +1 -1
  32. package/dist/webpack-loader.cjs +614 -68
  33. package/dist/webpack-loader.cjs.map +1 -1
  34. package/dist/webpack-loader.d.cts +2 -2
  35. package/dist/webpack-loader.d.ts +2 -2
  36. package/dist/webpack-loader.js +4 -4
  37. package/dist/worker.cjs +5955 -0
  38. package/dist/worker.cjs.map +1 -0
  39. package/dist/worker.d.cts +2 -0
  40. package/dist/worker.d.ts +2 -0
  41. package/dist/worker.js +72 -0
  42. package/dist/worker.js.map +1 -0
  43. package/package.json +4 -2
  44. package/dist/chunk-DNHOGPYV.js.map +0 -1
  45. package/dist/chunk-DOQEBGWB.js.map +0 -1
  46. package/dist/chunk-DWLB7FRR.js.map +0 -1
package/dist/cli.cjs CHANGED
@@ -175,8 +175,8 @@ init_cjs_shims();
175
175
 
176
176
  // ../cli/src/index.ts
177
177
  init_cjs_shims();
178
- var import_node_fs2 = require("fs");
179
- var path6 = __toESM(require("path"), 1);
178
+ var import_node_fs4 = require("fs");
179
+ var path9 = __toESM(require("path"), 1);
180
180
 
181
181
  // ../cli/src/options.ts
182
182
  init_cjs_shims();
@@ -192,6 +192,14 @@ function toSafety(raw) {
192
192
  if (n === 0 || n === 1 || n === 2 || n === 3) return n;
193
193
  throw new Error(`domflax: invalid --safety "${raw}" (expected 0, 1, 2 or 3)`);
194
194
  }
195
+ function toPositiveInt(raw, flag) {
196
+ if (raw === void 0) return null;
197
+ const n = Number(raw);
198
+ if (!Number.isInteger(n) || n < 1) {
199
+ throw new Error(`domflax: invalid ${flag} "${raw}" (expected a positive integer)`);
200
+ }
201
+ return n;
202
+ }
195
203
  function parseInvocation(argv) {
196
204
  const { values, positionals } = (0, import_node_util.parseArgs)({
197
205
  args: argv,
@@ -207,7 +215,9 @@ function parseInvocation(argv) {
207
215
  "no-interactive": { type: "boolean", default: false },
208
216
  yes: { type: "boolean", short: "y", default: false },
209
217
  safety: { type: "string" },
210
- "project-root": { type: "string" }
218
+ "project-root": { type: "string" },
219
+ "max-memory": { type: "string" },
220
+ concurrency: { type: "string" }
211
221
  }
212
222
  });
213
223
  const provider = values.provider ?? DEFAULT_PROVIDER;
@@ -226,7 +236,9 @@ function parseInvocation(argv) {
226
236
  interactive: values["no-interactive"] !== true && values.yes !== true,
227
237
  passes: null,
228
238
  safety: toSafety(values.safety),
229
- projectRoot: values["project-root"] ?? null
239
+ projectRoot: values["project-root"] ?? null,
240
+ maxMemory: toPositiveInt(values["max-memory"], "--max-memory"),
241
+ concurrency: toPositiveInt(values.concurrency, "--concurrency")
230
242
  };
231
243
  }
232
244
  function shouldPrompt(options, isTty) {
@@ -250,18 +262,212 @@ var USAGE = [
250
262
  " --dangerously-overwrite-source overwrite source in place (needs a clean git tree)",
251
263
  " --no-git-check skip the clean-git-tree gate",
252
264
  " --safety <0|1|2|3> optimization aggressiveness (default: 2)",
265
+ " --max-memory <MB> memory budget; caps pool RAM AND parallelism (default: ~70% free RAM)",
266
+ " --concurrency <N> max parallel workers (still clamped by --max-memory)",
253
267
  " --yes, --no-interactive never launch the wizard (CI-safe)",
254
268
  "",
269
+ "Many files are processed across CPU cores by a memory-bounded worker pool; small jobs run inline.",
255
270
  "With no paths in an interactive terminal, a guided wizard launches."
256
271
  ].join("\n");
257
272
 
273
+ // ../cli/src/pool.ts
274
+ init_cjs_shims();
275
+ var import_node_fs = require("fs");
276
+ var os = __toESM(require("os"), 1);
277
+ var path = __toESM(require("path"), 1);
278
+ var import_node_url = require("url");
279
+ var import_node_worker_threads = require("worker_threads");
280
+ var PER_WORKER_MB = 160;
281
+ var MIN_OLD_GEN_MB = 64;
282
+ function emptyTotals() {
283
+ return { files: 0, changed: 0, nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
284
+ }
285
+ function addStats(totals, stats, changed) {
286
+ totals.files += 1;
287
+ if (changed) totals.changed += 1;
288
+ totals.nodesRemoved += stats.nodesRemoved;
289
+ totals.classesSaved += stats.classesSaved;
290
+ totals.bytesSaved += stats.bytesSaved;
291
+ }
292
+ function clamp(n, lo, hi) {
293
+ return Math.max(lo, Math.min(hi, n));
294
+ }
295
+ function computeWorkerCount(options) {
296
+ const cpus2 = Math.max(1, os.cpus().length);
297
+ const freeMB = Math.floor(os.freemem() / (1024 * 1024));
298
+ const budgetMB = Math.max(PER_WORKER_MB, options.maxMemory ?? Math.floor(freeMB * 0.7));
299
+ const byMemory = Math.max(1, Math.floor(budgetMB / PER_WORKER_MB));
300
+ const target = options.concurrency ?? Math.max(1, cpus2 - 1);
301
+ const workers = clamp(Math.min(target, byMemory), 1, byMemory);
302
+ const perWorkerCapMB = Math.max(MIN_OLD_GEN_MB, Math.floor(budgetMB / workers));
303
+ return { workers, budgetMB, perWorkerCapMB };
304
+ }
305
+ function inlineThreshold(workers) {
306
+ return Math.max(4, 2 * workers);
307
+ }
308
+ function shouldUsePool(fileCount, plan) {
309
+ return plan.workers > 1 && fileCount > inlineThreshold(plan.workers);
310
+ }
311
+ function moduleDir() {
312
+ try {
313
+ return path.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
314
+ } catch {
315
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
316
+ }
317
+ }
318
+ function resolveWorkerPath() {
319
+ const dir = moduleDir();
320
+ const candidates = [
321
+ path.join(dir, "worker.cjs"),
322
+ path.join(dir, "worker.js"),
323
+ path.join(dir, "..", "dist", "worker.cjs"),
324
+ path.join(dir, "..", "dist", "worker.js")
325
+ ];
326
+ for (const c of candidates) {
327
+ if ((0, import_node_fs.existsSync)(c)) return c;
328
+ }
329
+ return candidates[0];
330
+ }
331
+ function runPool(files, init, plan, onWrote) {
332
+ const workerPath = resolveWorkerPath();
333
+ const totals = emptyTotals();
334
+ const wrote = [];
335
+ const errors = [];
336
+ let failures = 0;
337
+ const budgetBytes = plan.budgetMB * 1024 * 1024;
338
+ let nextIndex = 0;
339
+ let completed = 0;
340
+ const total = files.length;
341
+ let respawns = 0;
342
+ const maxRespawns = total + plan.workers + 8;
343
+ return new Promise((resolve6) => {
344
+ const handles = /* @__PURE__ */ new Set();
345
+ const finishIfDone = () => {
346
+ if (completed < total) return;
347
+ for (const h2 of handles) {
348
+ if (!h2.dead) {
349
+ try {
350
+ h2.worker.postMessage({ type: "stop" });
351
+ } catch {
352
+ }
353
+ void h2.worker.terminate();
354
+ }
355
+ }
356
+ handles.clear();
357
+ resolve6({ totals, failures, wrote, errors });
358
+ };
359
+ const recordFailure = (file, error) => {
360
+ failures += 1;
361
+ completed += 1;
362
+ errors.push({ path: file, error });
363
+ };
364
+ const dispatch = (h2) => {
365
+ if (h2.dead) return;
366
+ if (nextIndex >= total) {
367
+ h2.current = null;
368
+ try {
369
+ h2.worker.postMessage({ type: "stop" });
370
+ } catch {
371
+ }
372
+ return;
373
+ }
374
+ if (process.memoryUsage().rss > budgetBytes) {
375
+ setTimeout(() => dispatch(h2), 25);
376
+ return;
377
+ }
378
+ const file = files[nextIndex++];
379
+ h2.current = file;
380
+ try {
381
+ h2.worker.postMessage({ type: "file", path: file });
382
+ } catch {
383
+ onWorkerDown(h2);
384
+ }
385
+ };
386
+ const onMessage = (h2, msg) => {
387
+ if (msg.type === "ready") {
388
+ h2.ready = true;
389
+ dispatch(h2);
390
+ return;
391
+ }
392
+ h2.current = null;
393
+ if (msg.ok) {
394
+ addStats(totals, msg.stats, msg.changed);
395
+ if (msg.wrote) {
396
+ wrote.push(msg.wrote);
397
+ onWrote?.(msg.wrote);
398
+ }
399
+ } else {
400
+ failures += 1;
401
+ errors.push({ path: msg.path, error: msg.error });
402
+ }
403
+ completed += 1;
404
+ if (completed >= total) {
405
+ finishIfDone();
406
+ return;
407
+ }
408
+ dispatch(h2);
409
+ };
410
+ const drainRemaining = (reason) => {
411
+ while (nextIndex < total) recordFailure(files[nextIndex++], reason);
412
+ finishIfDone();
413
+ };
414
+ const onWorkerDown = (h2) => {
415
+ if (h2.dead) return;
416
+ h2.dead = true;
417
+ handles.delete(h2);
418
+ const lost = h2.current;
419
+ h2.current = null;
420
+ if (lost !== null) recordFailure(lost, "worker crashed while processing this file");
421
+ void h2.worker.terminate();
422
+ if (completed >= total) {
423
+ finishIfDone();
424
+ return;
425
+ }
426
+ if (nextIndex >= total) {
427
+ if (handles.size === 0) finishIfDone();
428
+ return;
429
+ }
430
+ if (respawns < maxRespawns) {
431
+ respawns += 1;
432
+ spawn();
433
+ } else if (handles.size === 0) {
434
+ drainRemaining("worker pool exhausted its respawn budget (memory cap too small?)");
435
+ }
436
+ };
437
+ const spawn = () => {
438
+ let worker;
439
+ try {
440
+ worker = new import_node_worker_threads.Worker(workerPath, {
441
+ workerData: init,
442
+ resourceLimits: { maxOldGenerationSizeMb: plan.perWorkerCapMB }
443
+ });
444
+ } catch {
445
+ if (nextIndex < total) recordFailure(files[nextIndex++], "failed to spawn worker");
446
+ if (completed >= total) finishIfDone();
447
+ else if (handles.size === 0 && nextIndex < total) spawn();
448
+ return;
449
+ }
450
+ const h2 = { worker, current: null, ready: false, dead: false };
451
+ handles.add(h2);
452
+ worker.on("message", (m2) => onMessage(h2, m2));
453
+ worker.on("error", () => onWorkerDown(h2));
454
+ worker.on("exit", (code) => {
455
+ if (code !== 0) onWorkerDown(h2);
456
+ });
457
+ };
458
+ const initial = Math.min(plan.workers, total);
459
+ for (let i = 0; i < initial; i++) spawn();
460
+ if (initial === 0) finishIfDone();
461
+ });
462
+ }
463
+
258
464
  // ../cli/src/safety.ts
259
465
  init_cjs_shims();
260
466
  var import_node_child_process = require("child_process");
261
- var path = __toESM(require("path"), 1);
467
+ var path2 = __toESM(require("path"), 1);
262
468
  var DISPOSABLE_DIRS = /* @__PURE__ */ new Set(["dist", "build", "out", ".next"]);
263
469
  function isDisposablePath(file) {
264
- return path.resolve(file).split(path.sep).some((seg) => DISPOSABLE_DIRS.has(seg));
470
+ return path2.resolve(file).split(path2.sep).some((seg) => DISPOSABLE_DIRS.has(seg));
265
471
  }
266
472
  function isGitClean(cwd) {
267
473
  try {
@@ -285,19 +491,19 @@ function planWrites(options, gitClean) {
285
491
  }
286
492
  return { ok: true, value: { mode: "overwrite-source", outDir: null } };
287
493
  }
288
- const outDir = path.resolve(options.out ?? "domflax-out");
494
+ const outDir = path2.resolve(options.out ?? "domflax-out");
289
495
  return { ok: true, value: { mode: "out-dir", outDir } };
290
496
  }
291
497
  function destinationFor(file, inputRoot, plan) {
292
- const absFile = path.resolve(file);
498
+ const absFile = path2.resolve(file);
293
499
  if (plan.mode === "overwrite-source") {
294
500
  return { ok: true, value: absFile };
295
501
  }
296
502
  const outDir = plan.outDir;
297
- const rel = path.relative(inputRoot, absFile);
298
- const safeRel = rel === "" || rel.startsWith("..") || path.isAbsolute(rel) ? path.basename(absFile) : rel;
299
- const dest = path.join(outDir, safeRel);
300
- if (path.resolve(dest) === absFile && !isDisposablePath(absFile)) {
503
+ const rel = path2.relative(inputRoot, absFile);
504
+ const safeRel = rel === "" || rel.startsWith("..") || path2.isAbsolute(rel) ? path2.basename(absFile) : rel;
505
+ const dest = path2.join(outDir, safeRel);
506
+ if (path2.resolve(dest) === absFile && !isDisposablePath(absFile)) {
301
507
  return {
302
508
  ok: false,
303
509
  error: `refusing to overwrite source file ${absFile}: the output path resolves onto the source. Choose a different --out, or pass --dangerously-overwrite-source (with a clean git tree).`
@@ -382,6 +588,7 @@ function defaultMeta(safetyFloor = 0) {
382
588
  declaresCustomProperties: false,
383
589
  whitespaceSensitive: false,
384
590
  touched: false,
591
+ styleDirty: false,
385
592
  synthetic: false,
386
593
  safetyFloor
387
594
  };
@@ -566,6 +773,14 @@ function markTouched(state, id) {
566
773
  state.touched.add(id);
567
774
  }
568
775
  }
776
+ function markStyleDirty(state, id) {
777
+ const n = state.doc.nodes.get(id);
778
+ if (n) {
779
+ n.meta.touched = true;
780
+ n.meta.styleDirty = true;
781
+ state.touched.add(id);
782
+ }
783
+ }
569
784
  function removeSubtree(state, id) {
570
785
  const node = state.doc.nodes.get(id);
571
786
  if (!node) return;
@@ -858,7 +1073,7 @@ function applyOne(state, op) {
858
1073
  return [precond(op, op.target, "setClassList target is not an element")];
859
1074
  }
860
1075
  el.computed = cloneStyleMap(op.style);
861
- markTouched(state, op.target);
1076
+ markStyleDirty(state, op.target);
862
1077
  return [];
863
1078
  }
864
1079
  case "mergeStyle": {
@@ -881,7 +1096,7 @@ function applyOne(state, op) {
881
1096
  const src = doc.nodes.get(op.source);
882
1097
  if (src) markTouched(state, op.source);
883
1098
  }
884
- markTouched(state, op.target);
1099
+ markStyleDirty(state, op.target);
885
1100
  return [];
886
1101
  }
887
1102
  case "foldInheritedStyles":
@@ -932,7 +1147,7 @@ function applyFold(state, op) {
932
1147
  }
933
1148
  if (folded) {
934
1149
  into.computed = { blocks: nextBlocks };
935
- markTouched(state, intoId);
1150
+ markStyleDirty(state, intoId);
936
1151
  }
937
1152
  }
938
1153
  for (const d3 of issues) state.diagnostics.push(d3);
@@ -1197,8 +1412,123 @@ init_cjs_shims();
1197
1412
  var DISPLAY = "display";
1198
1413
  var POSITION = "position";
1199
1414
  var TRANSFORM = "transform";
1415
+ var ALIGN_ITEMS = "align-items";
1416
+ var JUSTIFY_CONTENT = "justify-content";
1417
+ var JUSTIFY_ITEMS = "justify-items";
1418
+ var PLACE_ITEMS = "place-items";
1419
+ var PLACE_SELF = "place-self";
1200
1420
  var CONTEXT_SAFE_DISPLAYS = /* @__PURE__ */ new Set(["block", "contents", ""]);
1201
1421
  var STATIC_POSITIONS = /* @__PURE__ */ new Set(["static", ""]);
1422
+ var CENTERING_DISPLAYS = /* @__PURE__ */ new Set(["flex", "grid"]);
1423
+ var GRID_PARENT_DISPLAYS = /* @__PURE__ */ new Set(["grid"]);
1424
+ var STRETCHY_ITEM_ALIGN = /* @__PURE__ */ new Set(["normal", "stretch"]);
1425
+ var PARENT_ITEMS_ALIGN_PROPS = [ALIGN_ITEMS, JUSTIFY_ITEMS, PLACE_ITEMS];
1426
+ function isBaseCondition(block) {
1427
+ const c = block.condition;
1428
+ return c.media === "" && c.states.length === 0 && c.pseudoElement === "";
1429
+ }
1430
+ function baseValue(sm, prop) {
1431
+ for (const block of sm.blocks.values()) {
1432
+ if (!isBaseCondition(block)) continue;
1433
+ const d3 = block.decls.get(prop);
1434
+ return d3 ? String(d3.value) : null;
1435
+ }
1436
+ return null;
1437
+ }
1438
+ function parentIsFillingGrid(before, wrapper, norm) {
1439
+ if (wrapper.parent == null) return false;
1440
+ const p2 = before.nodes.get(wrapper.parent);
1441
+ if (!p2 || p2.kind !== "element") return false;
1442
+ const pc2 = norm.normalizeStyleMap(p2.computed);
1443
+ let baseIsGrid = false;
1444
+ for (const block of pc2.blocks.values()) {
1445
+ const disp = block.decls.get(DISPLAY);
1446
+ if (disp) {
1447
+ if (!GRID_PARENT_DISPLAYS.has(String(disp.value))) return false;
1448
+ if (isBaseCondition(block)) baseIsGrid = true;
1449
+ }
1450
+ for (const prop of PARENT_ITEMS_ALIGN_PROPS) {
1451
+ const d3 = block.decls.get(prop);
1452
+ if (d3 && !STRETCHY_ITEM_ALIGN.has(String(d3.value))) return false;
1453
+ }
1454
+ }
1455
+ return baseIsGrid;
1456
+ }
1457
+ function wrapperHasOnlyCenteringStyle(wrapperComputed, childComputed, norm) {
1458
+ for (const block of wrapperComputed.blocks.values()) {
1459
+ const base = isBaseCondition(block);
1460
+ const ck = conditionKey(block.condition);
1461
+ for (const [prop, decl] of block.decls) {
1462
+ const val = String(decl.value);
1463
+ if (prop === DISPLAY) {
1464
+ if (base && CENTERING_DISPLAYS.has(val)) continue;
1465
+ return false;
1466
+ }
1467
+ if (prop === ALIGN_ITEMS) {
1468
+ if (base && val === "center") continue;
1469
+ return false;
1470
+ }
1471
+ if (prop === JUSTIFY_CONTENT) {
1472
+ if (base && val === "center") continue;
1473
+ return false;
1474
+ }
1475
+ if (prop === POSITION) {
1476
+ if (STATIC_POSITIONS.has(val)) continue;
1477
+ return false;
1478
+ }
1479
+ if (prop === TRANSFORM) {
1480
+ if (val === "none") continue;
1481
+ return false;
1482
+ }
1483
+ if (isInherited2(decl, norm)) continue;
1484
+ if (childReproduces(childComputed, ck, prop, val)) continue;
1485
+ return false;
1486
+ }
1487
+ }
1488
+ return true;
1489
+ }
1490
+ function wrapperCentersSingleElement(before, wrapper) {
1491
+ let elements = 0;
1492
+ for (const cid of wrapper.children) {
1493
+ const n = before.nodes.get(cid);
1494
+ if (!n) continue;
1495
+ if (n.kind === "element") {
1496
+ elements += 1;
1497
+ continue;
1498
+ }
1499
+ if (n.kind === "comment") continue;
1500
+ if (n.kind === "text" && n.value.trim() === "") continue;
1501
+ return false;
1502
+ }
1503
+ return elements === 1;
1504
+ }
1505
+ function childHasSelfAlign(childBefore, norm) {
1506
+ if (!childBefore) return false;
1507
+ const sm = norm.normalizeStyleMap(childBefore);
1508
+ for (const block of sm.blocks.values()) {
1509
+ for (const prop of SELF_ALIGN_PROPS) {
1510
+ const d3 = block.decls.get(prop);
1511
+ if (d3 && !NEUTRAL_ALIGN.has(String(d3.value))) return true;
1512
+ }
1513
+ }
1514
+ return false;
1515
+ }
1516
+ function childGainsPlaceSelfCenter(childAfter) {
1517
+ if (baseValue(childAfter, PLACE_SELF) === "center") return true;
1518
+ return baseValue(childAfter, "align-self") === "center" && baseValue(childAfter, "justify-self") === "center";
1519
+ }
1520
+ function isContextCompensatedCentering(before, wrapper, wrapperComputed, childBefore, childAfter, norm) {
1521
+ if (!childAfter) return false;
1522
+ if (!CENTERING_DISPLAYS.has(baseValue(wrapperComputed, DISPLAY) ?? "")) return false;
1523
+ if (baseValue(wrapperComputed, ALIGN_ITEMS) !== "center") return false;
1524
+ if (baseValue(wrapperComputed, JUSTIFY_CONTENT) !== "center") return false;
1525
+ const childAfterNorm = norm.normalizeStyleMap(childAfter);
1526
+ if (!childGainsPlaceSelfCenter(childAfterNorm)) return false;
1527
+ if (!wrapperHasOnlyCenteringStyle(wrapperComputed, childAfterNorm, norm)) return false;
1528
+ if (!wrapperCentersSingleElement(before, wrapper)) return false;
1529
+ if (childHasSelfAlign(childBefore, norm)) return false;
1530
+ return parentIsFillingGrid(before, wrapper, norm);
1531
+ }
1202
1532
  var SELF_ALIGN_PROPS = [
1203
1533
  "place-self",
1204
1534
  "align-self",
@@ -1298,6 +1628,9 @@ function classifyFlattenOps(before, after, ops, norm) {
1298
1628
  const wrapperComputed = norm.normalizeStyleMap(wrapper.computed);
1299
1629
  const childAfter = childId != null ? getElement(after, childId)?.computed ?? null : null;
1300
1630
  const childBefore = childId != null ? getElement(before, childId)?.computed ?? null : null;
1631
+ if (isContextCompensatedCentering(before, wrapper, wrapperComputed, childBefore, childAfter, norm)) {
1632
+ return { kind: "provably-safe", wrapperId, childId };
1633
+ }
1301
1634
  if (establishesChildContext(wrapperComputed)) {
1302
1635
  return { kind: "needs-verification", wrapperId, childId };
1303
1636
  }
@@ -1579,15 +1912,34 @@ function sameTokens(a, b3) {
1579
1912
  for (let i = 0; i < a.length; i += 1) if (a[i] !== b3[i]) return false;
1580
1913
  return true;
1581
1914
  }
1915
+ function residualStyle(computed2, covered, norm) {
1916
+ const cov = norm.normalizeStyleMap(covered);
1917
+ const blocks = /* @__PURE__ */ new Map();
1918
+ for (const [key, block] of norm.normalizeStyleMap(computed2).blocks) {
1919
+ const covBlock = cov.blocks.get(key);
1920
+ const decls = /* @__PURE__ */ new Map();
1921
+ for (const [prop, decl] of block.decls) {
1922
+ const covDecl = covBlock?.decls.get(prop);
1923
+ if (covDecl && covDecl.value === decl.value && covDecl.important === decl.important) continue;
1924
+ decls.set(prop, decl);
1925
+ }
1926
+ if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
1927
+ }
1928
+ return { blocks };
1929
+ }
1582
1930
  function syncClassesFromComputed(doc, resolver, norm) {
1583
1931
  const sink = createSyntheticSink();
1584
1932
  for (const id of elementIds(doc)) {
1585
1933
  const el = getElement(doc, id);
1586
- if (!el || !el.meta.touched) continue;
1934
+ if (!el) continue;
1935
+ if (!el.meta.styleDirty) continue;
1587
1936
  if (el.classes.opaque || el.classes.hasDynamic) continue;
1588
1937
  const tokens = staticTokensOf(el.classes);
1938
+ const retained = tokens.filter((t) => !resolver.selectorUsage(t).droppable);
1939
+ const covered = retained.length > 0 ? resolver.resolve({ classes: retained }).styles : null;
1940
+ const target = covered ? residualStyle(el.computed, covered, norm) : el.computed;
1589
1941
  const ctx = { normalizer: norm, sink };
1590
- const emitted = resolver.emit(el.computed, ctx).classes;
1942
+ const emitted = resolver.emit(target, ctx).classes;
1591
1943
  if (emitted.length === 0) continue;
1592
1944
  const emittedSet = new Set(emitted);
1593
1945
  const next = [];
@@ -1610,6 +1962,350 @@ function syncClassesFromComputed(doc, resolver, norm) {
1610
1962
  }
1611
1963
  }
1612
1964
 
1965
+ // ../frontend-html/src/index.ts
1966
+ init_cjs_shims();
1967
+
1968
+ // ../frontend-html/src/backend.ts
1969
+ init_cjs_shims();
1970
+ var import_magic_string = __toESM(require("magic-string"), 1);
1971
+ function staticTokensOf2(classes) {
1972
+ const out = [];
1973
+ for (const seg of classes.segments) {
1974
+ if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
1975
+ }
1976
+ return out;
1977
+ }
1978
+ function sameTokens2(a, b3) {
1979
+ if (a.length !== b3.length) return false;
1980
+ for (let i = 0; i < a.length; i += 1) if (a[i] !== b3[i]) return false;
1981
+ return true;
1982
+ }
1983
+ function primarySource(doc) {
1984
+ for (const sf of doc.sources.values()) {
1985
+ if (typeof sf.text === "string" && sf.text.length > 0) return sf;
1986
+ }
1987
+ return null;
1988
+ }
1989
+ function collectKept(doc) {
1990
+ const out = [];
1991
+ const seen = /* @__PURE__ */ new Set();
1992
+ const visit = (id) => {
1993
+ if (seen.has(id)) return;
1994
+ seen.add(id);
1995
+ const n = doc.nodes.get(id);
1996
+ if (!n) return;
1997
+ out.push(n);
1998
+ if (n.kind === "element" || n.kind === "fragment") for (const c of n.children) visit(c);
1999
+ };
2000
+ visit(doc.root);
2001
+ return out;
2002
+ }
2003
+ function strictlyContains(a, b3) {
2004
+ if (a.file !== b3.file) return false;
2005
+ if (a.start <= b3.start && b3.end <= a.end) return !(a.start === b3.start && a.end === b3.end);
2006
+ return false;
2007
+ }
2008
+ function backrefIds(doc) {
2009
+ const out = [];
2010
+ const max = doc.alloc.peek;
2011
+ for (let i = 1; i < max; i += 1) {
2012
+ const id = i;
2013
+ if (doc.backref.get(id)) out.push(id);
2014
+ }
2015
+ return out;
2016
+ }
2017
+ function currentTokens(sf, valueSpan) {
2018
+ const raw = sf.text.slice(valueSpan.start, valueSpan.end).trim();
2019
+ const unquoted = raw.replace(/^['"]/, "").replace(/['"]$/, "");
2020
+ return unquoted.split(/\s+/).filter((t) => t.length > 0);
2021
+ }
2022
+ function editClasses(ms, doc, sf, el) {
2023
+ const classes = el.classes;
2024
+ if (classes.hasDynamic || classes.opaque) return false;
2025
+ const tokens = staticTokensOf2(classes);
2026
+ const valueSpan = classes.valueSpan;
2027
+ if (valueSpan && valueSpan.file === sf.id) {
2028
+ if (sameTokens2(currentTokens(sf, valueSpan), tokens)) return false;
2029
+ const current = sf.text.slice(valueSpan.start, valueSpan.end).trim();
2030
+ const quote = current.startsWith("'") ? "'" : '"';
2031
+ ms.overwrite(valueSpan.start, valueSpan.end, `${quote}${tokens.join(" ")}${quote}`);
2032
+ return true;
2033
+ }
2034
+ if (tokens.length === 0) return false;
2035
+ const openTag = doc.backref.get(el.id)?.openTagSpan;
2036
+ if (!openTag || openTag.file !== sf.id) return false;
2037
+ ms.appendLeft(openTag.start + 1 + el.tag.length, ` class="${tokens.join(" ")}"`);
2038
+ return true;
2039
+ }
2040
+ function surgicalPrint(doc) {
2041
+ const sf = primarySource(doc);
2042
+ if (!sf) return null;
2043
+ const ms = new import_magic_string.default(sf.text);
2044
+ const kept = collectKept(doc);
2045
+ const keptSpans = [];
2046
+ for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
2047
+ const removed = [];
2048
+ for (const id of backrefIds(doc)) {
2049
+ if (doc.nodes.has(id)) continue;
2050
+ const back = doc.backref.get(id);
2051
+ if (!back || back.span.file !== sf.id) continue;
2052
+ const unwrapped = keptSpans.some((k3) => strictlyContains(back.span, k3));
2053
+ removed.push({ backref: back, unwrapped });
2054
+ }
2055
+ const fullRemovals = removed.filter((r2) => !r2.unwrapped).map((r2) => r2.backref.span);
2056
+ for (const r2 of removed) {
2057
+ const s = r2.backref.span;
2058
+ if (fullRemovals.some((f) => f !== s && strictlyContains(f, s))) continue;
2059
+ if (r2.unwrapped) {
2060
+ const open = r2.backref.openTagSpan;
2061
+ const close = r2.backref.closeTagSpan;
2062
+ if (open && open.file === sf.id && open.end > open.start) ms.remove(open.start, open.end);
2063
+ if (close && close.file === sf.id && close.end > close.start) ms.remove(close.start, close.end);
2064
+ } else {
2065
+ ms.remove(s.start, s.end);
2066
+ }
2067
+ }
2068
+ for (const n of kept) if (n.kind === "element") editClasses(ms, doc, sf, n);
2069
+ return ms.toString();
2070
+ }
2071
+ function doPrint(doc) {
2072
+ return surgicalPrint(doc) ?? "";
2073
+ }
2074
+
2075
+ // ../frontend-html/src/parse.ts
2076
+ init_cjs_shims();
2077
+ var import_node_module = require("module");
2078
+
2079
+ // ../frontend-html/src/walk.ts
2080
+ init_cjs_shims();
2081
+ var HTML_LANGS = ["html"];
2082
+ var FILE_ID = 1;
2083
+ function looksLikeHtml(id, code) {
2084
+ if (/\.html?$/i.test(id)) return true;
2085
+ const head = code.slice(0, 256).trimStart().toLowerCase();
2086
+ return head.startsWith("<!doctype html") || head.startsWith("<html") || head.startsWith("<");
2087
+ }
2088
+ var OPAQUE_SUBTREE_TAGS = /* @__PURE__ */ new Set([
2089
+ "script",
2090
+ "style",
2091
+ "template",
2092
+ "svg",
2093
+ "pre",
2094
+ "textarea"
2095
+ ]);
2096
+ function isOpaqueSubtreeTag(tag) {
2097
+ return OPAQUE_SUBTREE_TAGS.has(tag);
2098
+ }
2099
+ function elementIsOpaque(attrs) {
2100
+ for (const a of attrs) {
2101
+ const n = a.name.toLowerCase();
2102
+ if (n === "id" || n === "contenteditable") return true;
2103
+ if (n.startsWith("on")) return true;
2104
+ }
2105
+ return false;
2106
+ }
2107
+ function hasEventHandler(attrs) {
2108
+ for (const a of attrs) if (/^on/i.test(a.name)) return true;
2109
+ return false;
2110
+ }
2111
+ function span(start, end) {
2112
+ return { file: FILE_ID, start, end };
2113
+ }
2114
+ function attrsLocOf(loc) {
2115
+ if (!loc) return void 0;
2116
+ return loc.startTag?.attrs ?? loc.attrs;
2117
+ }
2118
+ function classValueSpan(loc, source) {
2119
+ const attrsLoc = attrsLocOf(loc);
2120
+ const cl = attrsLoc?.["class"];
2121
+ if (!cl) return null;
2122
+ const text = source.slice(cl.startOffset, cl.endOffset);
2123
+ const eq = text.indexOf("=");
2124
+ if (eq === -1) return null;
2125
+ let i = eq + 1;
2126
+ while (i < text.length && /\s/.test(text[i])) i += 1;
2127
+ if (i >= text.length) return null;
2128
+ return span(cl.startOffset + i, cl.endOffset);
2129
+ }
2130
+
2131
+ // ../frontend-html/src/parse.ts
2132
+ var cachedParse5 = null;
2133
+ function loadParse5() {
2134
+ if (cachedParse5) return cachedParse5;
2135
+ const req = (0, import_node_module.createRequire)(importMetaUrl);
2136
+ cachedParse5 = req("parse5");
2137
+ return cachedParse5;
2138
+ }
2139
+ function doParse(code, ctx) {
2140
+ const diagnostics = [];
2141
+ const doc = createDocument("html");
2142
+ const backref = doc.backref;
2143
+ const parse5 = loadParse5();
2144
+ const document2 = parse5.parse(code, { sourceCodeLocationInfo: true });
2145
+ const eol = code.includes("\r\n") ? "\r\n" : "\n";
2146
+ const sourceFile = {
2147
+ id: FILE_ID,
2148
+ path: ctx.id,
2149
+ text: code,
2150
+ frontend: "html",
2151
+ eol,
2152
+ indentUnit: " ",
2153
+ native: document2
2154
+ };
2155
+ doc.sources.set(FILE_ID, sourceFile);
2156
+ const resolveComputed = (tokens, tag, nodeId) => {
2157
+ if (tokens.length === 0) return emptyStyleMap();
2158
+ const res = ctx.resolver.resolve({ classes: tokens, element: { tagName: tag, namespace: "html" } });
2159
+ for (const w2 of res.warnings) {
2160
+ diagnostics.push({
2161
+ code: "DF_STYLE_CONFLICT_UNRESOLVED",
2162
+ severity: w2.severity,
2163
+ message: w2.message,
2164
+ nodeId
2165
+ });
2166
+ }
2167
+ return ctx.normalizer.normalizeStyleMap(res.styles);
2168
+ };
2169
+ const splitTokens = (raw) => raw.split(/\s+/).filter((t) => t.length > 0);
2170
+ const appendChild = (node, parentId, out) => {
2171
+ const name = node.nodeName;
2172
+ if (name === "#text") {
2173
+ const value = node.value ?? "";
2174
+ const id = doc.alloc.next();
2175
+ const loc = node.sourceCodeLocation ?? null;
2176
+ doc.nodes.set(
2177
+ id,
2178
+ createText(id, value, {
2179
+ parent: parentId,
2180
+ span: loc ? span(loc.startOffset, loc.endOffset) : null,
2181
+ collapsible: /^\s*$/.test(value)
2182
+ })
2183
+ );
2184
+ out.push(id);
2185
+ return;
2186
+ }
2187
+ if (name === "#comment") {
2188
+ const id = doc.alloc.next();
2189
+ const loc = node.sourceCodeLocation ?? null;
2190
+ doc.nodes.set(
2191
+ id,
2192
+ createComment(id, node.data ?? "", {
2193
+ parent: parentId,
2194
+ span: loc ? span(loc.startOffset, loc.endOffset) : null
2195
+ })
2196
+ );
2197
+ out.push(id);
2198
+ return;
2199
+ }
2200
+ if (name === "#documentType") return;
2201
+ if (name.startsWith("#")) {
2202
+ for (const c of node.childNodes ?? []) appendChild(c, parentId, out);
2203
+ return;
2204
+ }
2205
+ out.push(buildElement(node, parentId));
2206
+ };
2207
+ const buildElement = (node, parentId) => {
2208
+ const id = doc.alloc.next();
2209
+ const tag = (node.tagName ?? node.nodeName).toLowerCase();
2210
+ const loc = node.sourceCodeLocation ?? null;
2211
+ const attrsArr = node.attrs ?? [];
2212
+ const opaqueSubtree = isOpaqueSubtreeTag(tag);
2213
+ const synthetic = loc == null;
2214
+ const opaque2 = opaqueSubtree || synthetic || elementIsOpaque(attrsArr);
2215
+ const meta = defaultMeta();
2216
+ meta.hasEventHandlers = hasEventHandler(attrsArr);
2217
+ meta.safetyFloor = opaque2 ? 0 : 3;
2218
+ let classes = emptyClassList();
2219
+ let classTokens = [];
2220
+ const entries = /* @__PURE__ */ new Map();
2221
+ const order = [];
2222
+ for (const a of attrsArr) {
2223
+ if (a.name.toLowerCase() === "class") {
2224
+ classTokens = splitTokens(a.value);
2225
+ const valueSpan = classValueSpan(loc, code);
2226
+ const clAttr = attrsLocOf(loc)?.["class"];
2227
+ const seg = {
2228
+ kind: "static",
2229
+ span: valueSpan ?? void 0,
2230
+ tokens: classTokens.map((value) => ({ value }))
2231
+ };
2232
+ classes = {
2233
+ form: "string-literal",
2234
+ segments: [seg],
2235
+ valueSpan,
2236
+ attrSpan: clAttr ? span(clAttr.startOffset, clAttr.endOffset) : void 0,
2237
+ hasDynamic: false,
2238
+ opaque: false,
2239
+ rewritable: valueSpan != null
2240
+ };
2241
+ continue;
2242
+ }
2243
+ const v2 = a.value;
2244
+ entries.set(a.name, { kind: "static", value: v2 === "" ? true : v2 });
2245
+ order.push(a.name);
2246
+ }
2247
+ const attrs = { entries, spreads: [], order };
2248
+ const computed2 = resolveComputed(classTokens, tag, id);
2249
+ const children = [];
2250
+ if (!opaqueSubtree) {
2251
+ for (const c of node.childNodes ?? []) appendChild(c, id, children);
2252
+ }
2253
+ const el = createElement(id, {
2254
+ tag,
2255
+ namespace: "html",
2256
+ isComponent: false,
2257
+ selfClosing: loc ? loc.endTag == null : false,
2258
+ classes,
2259
+ computed: computed2,
2260
+ attrs,
2261
+ children,
2262
+ parent: parentId,
2263
+ span: loc ? span(loc.startOffset, loc.endOffset) : null,
2264
+ meta
2265
+ });
2266
+ doc.nodes.set(id, el);
2267
+ if (loc) {
2268
+ backref.set(id, {
2269
+ nodeId: id,
2270
+ span: span(loc.startOffset, loc.endOffset),
2271
+ openTagSpan: loc.startTag ? span(loc.startTag.startOffset, loc.startTag.endOffset) : null,
2272
+ closeTagSpan: loc.endTag ? span(loc.endTag.startOffset, loc.endTag.endOffset) : null,
2273
+ innerSpan: null,
2274
+ selfClosing: loc.endTag == null
2275
+ });
2276
+ }
2277
+ return id;
2278
+ };
2279
+ const rootFrag = doc.nodes.get(doc.root);
2280
+ appendChild(document2, doc.root, rootFrag.children);
2281
+ return { doc, diagnostics };
2282
+ }
2283
+
2284
+ // ../frontend-html/src/index.ts
2285
+ var htmlFrontend = {
2286
+ name: "html",
2287
+ langs: HTML_LANGS,
2288
+ canParse(id, code) {
2289
+ return looksLikeHtml(id, code);
2290
+ },
2291
+ parse(code, ctx) {
2292
+ return doParse(code, ctx);
2293
+ }
2294
+ };
2295
+ function createHtmlFrontend() {
2296
+ return htmlFrontend;
2297
+ }
2298
+ var htmlBackend = {
2299
+ name: "html",
2300
+ langs: HTML_LANGS,
2301
+ print(doc, _plan, _ctx) {
2302
+ return { code: doPrint(doc), map: null, edits: [], diagnostics: [] };
2303
+ }
2304
+ };
2305
+ function createHtmlBackend() {
2306
+ return htmlBackend;
2307
+ }
2308
+
1613
2309
  // ../frontend-jsx/src/index.ts
1614
2310
  init_cjs_shims();
1615
2311
 
@@ -1621,7 +2317,7 @@ init_cjs_shims();
1621
2317
  var import_traverse = __toESM(require("@babel/traverse"), 1);
1622
2318
  var traverse = typeof import_traverse.default === "function" ? import_traverse.default : import_traverse.default.default;
1623
2319
  var JSX_LANGS = ["jsx", "tsx"];
1624
- var FILE_ID = 1;
2320
+ var FILE_ID2 = 1;
1625
2321
  function jsxName(node) {
1626
2322
  switch (node.type) {
1627
2323
  case "JSXIdentifier":
@@ -1746,7 +2442,7 @@ function looksLikeJsx(id, code) {
1746
2442
  // ../frontend-jsx/src/frontend-parse.ts
1747
2443
  init_cjs_shims();
1748
2444
  var import_parser = require("@babel/parser");
1749
- function doParse(code, ctx) {
2445
+ function doParse2(code, ctx) {
1750
2446
  const diagnostics = [];
1751
2447
  const doc = createDocument("jsx");
1752
2448
  const backref = doc.backref;
@@ -1756,7 +2452,7 @@ function doParse(code, ctx) {
1756
2452
  });
1757
2453
  const eol = code.includes("\r\n") ? "\r\n" : "\n";
1758
2454
  const sourceFile = {
1759
- id: FILE_ID,
2455
+ id: FILE_ID2,
1760
2456
  path: ctx.id,
1761
2457
  text: code,
1762
2458
  frontend: "jsx",
@@ -1764,23 +2460,23 @@ function doParse(code, ctx) {
1764
2460
  indentUnit: " ",
1765
2461
  native: ast
1766
2462
  };
1767
- doc.sources.set(FILE_ID, sourceFile);
2463
+ doc.sources.set(FILE_ID2, sourceFile);
1768
2464
  const spanOf = (node) => {
1769
2465
  if (node.start == null || node.end == null) return null;
1770
- const span = {
1771
- file: FILE_ID,
2466
+ const span2 = {
2467
+ file: FILE_ID2,
1772
2468
  start: node.start,
1773
2469
  end: node.end,
1774
2470
  startLoc: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : void 0,
1775
2471
  endLoc: node.loc ? { line: node.loc.end.line, column: node.loc.end.column } : void 0
1776
2472
  };
1777
- return span;
2473
+ return span2;
1778
2474
  };
1779
2475
  const sliceOf = (node) => node.start == null || node.end == null ? "" : code.slice(node.start, node.end);
1780
2476
  const internExpr = (node, spread) => {
1781
2477
  const payload = { text: sliceOf(node), spread };
1782
2478
  return doc.exprs.intern({
1783
- span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
2479
+ span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
1784
2480
  kind: exprKind(node),
1785
2481
  payload
1786
2482
  });
@@ -1826,7 +2522,7 @@ function doParse(code, ctx) {
1826
2522
  }
1827
2523
  return emptyClassList();
1828
2524
  };
1829
- const staticTokensOf3 = (classes) => {
2525
+ const staticTokensOf4 = (classes) => {
1830
2526
  const out = [];
1831
2527
  for (const seg of classes.segments) {
1832
2528
  if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
@@ -1904,7 +2600,7 @@ function doParse(code, ctx) {
1904
2600
  doc.nodes.set(id, createFragment(id, { children, parent: parentId, span: spanOf(node) }));
1905
2601
  backref.set(id, {
1906
2602
  nodeId: id,
1907
- span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
2603
+ span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
1908
2604
  openTagSpan: spanOf(node.openingFragment),
1909
2605
  closeTagSpan: spanOf(node.closingFragment),
1910
2606
  innerSpan: null,
@@ -1953,7 +2649,7 @@ function doParse(code, ctx) {
1953
2649
  }
1954
2650
  let computed2 = emptyStyleMap();
1955
2651
  if (!classes.hasDynamic) {
1956
- const tokens = staticTokensOf3(classes);
2652
+ const tokens = staticTokensOf4(classes);
1957
2653
  if (tokens.length > 0) {
1958
2654
  const res = ctx.resolver.resolve({
1959
2655
  classes: tokens,
@@ -1986,13 +2682,13 @@ function doParse(code, ctx) {
1986
2682
  });
1987
2683
  doc.nodes.set(id, el);
1988
2684
  const inner = children.length > 0 ? spanOf(node.children[0]) && spanOf(node.children.at(-1)) ? {
1989
- file: FILE_ID,
2685
+ file: FILE_ID2,
1990
2686
  start: spanOf(node.children[0]).start,
1991
2687
  end: spanOf(node.children.at(-1)).end
1992
2688
  } : null : null;
1993
2689
  backref.set(id, {
1994
2690
  nodeId: id,
1995
- span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
2691
+ span: spanOf(node) ?? { file: FILE_ID2, start: 0, end: 0 },
1996
2692
  openTagSpan: spanOf(opening),
1997
2693
  closeTagSpan: node.closingElement ? spanOf(node.closingElement) : null,
1998
2694
  innerSpan: inner,
@@ -2002,13 +2698,13 @@ function doParse(code, ctx) {
2002
2698
  };
2003
2699
  const roots = [];
2004
2700
  traverse(ast, {
2005
- JSXElement(path7) {
2006
- roots.push(path7.node);
2007
- path7.skip();
2701
+ JSXElement(path10) {
2702
+ roots.push(path10.node);
2703
+ path10.skip();
2008
2704
  },
2009
- JSXFragment(path7) {
2010
- roots.push(path7.node);
2011
- path7.skip();
2705
+ JSXFragment(path10) {
2706
+ roots.push(path10.node);
2707
+ path10.skip();
2012
2708
  }
2013
2709
  });
2014
2710
  const rootFrag = doc.nodes.get(doc.root);
@@ -2027,7 +2723,7 @@ var jsxFrontend = {
2027
2723
  return looksLikeJsx(id, code);
2028
2724
  },
2029
2725
  parse(code, ctx) {
2030
- return doParse(code, ctx);
2726
+ return doParse2(code, ctx);
2031
2727
  }
2032
2728
  };
2033
2729
  function createJsxFrontend() {
@@ -2036,7 +2732,7 @@ function createJsxFrontend() {
2036
2732
 
2037
2733
  // ../frontend-jsx/src/backend.ts
2038
2734
  init_cjs_shims();
2039
- var import_magic_string = __toESM(require("magic-string"), 1);
2735
+ var import_magic_string2 = __toESM(require("magic-string"), 1);
2040
2736
  var JSX_LANGS2 = ["jsx", "tsx"];
2041
2737
  function exprText(doc, ref) {
2042
2738
  const rec = doc.exprs.get(ref);
@@ -2050,20 +2746,20 @@ function exprText(doc, ref) {
2050
2746
  }
2051
2747
  return { text: "", spread: false };
2052
2748
  }
2053
- function staticTokensOf2(classes) {
2749
+ function staticTokensOf3(classes) {
2054
2750
  const out = [];
2055
2751
  for (const seg of classes.segments) {
2056
2752
  if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
2057
2753
  }
2058
2754
  return out;
2059
2755
  }
2060
- function primarySource(doc) {
2756
+ function primarySource2(doc) {
2061
2757
  for (const sf of doc.sources.values()) {
2062
2758
  if (typeof sf.text === "string" && sf.text.length > 0) return sf;
2063
2759
  }
2064
2760
  return null;
2065
2761
  }
2066
- function collectKept(doc) {
2762
+ function collectKept2(doc) {
2067
2763
  const out = [];
2068
2764
  const seen = /* @__PURE__ */ new Set();
2069
2765
  const visit = (id) => {
@@ -2077,15 +2773,15 @@ function collectKept(doc) {
2077
2773
  visit(doc.root);
2078
2774
  return out;
2079
2775
  }
2080
- function strictlyContains(a, b3) {
2776
+ function strictlyContains2(a, b3) {
2081
2777
  if (a.file !== b3.file) return false;
2082
2778
  if (a.start <= b3.start && b3.end <= a.end) return !(a.start === b3.start && a.end === b3.end);
2083
2779
  return false;
2084
2780
  }
2085
- function editClasses(ms, doc, sf, el) {
2781
+ function editClasses2(ms, doc, sf, el) {
2086
2782
  const classes = el.classes;
2087
2783
  if (classes.hasDynamic || classes.opaque) return false;
2088
- const tokens = staticTokensOf2(classes);
2784
+ const tokens = staticTokensOf3(classes);
2089
2785
  const valueSpan = classes.valueSpan;
2090
2786
  if (valueSpan && valueSpan.file === sf.id) {
2091
2787
  const current = sf.text.slice(valueSpan.start, valueSpan.end);
@@ -2149,10 +2845,10 @@ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
2149
2845
  const inside = [];
2150
2846
  for (const n of kept) {
2151
2847
  if (n.kind !== "element" || !n.span || n.span.file !== sf.id) continue;
2152
- if (strictlyContains(region.span, n.span)) inside.push(n);
2848
+ if (strictlyContains2(region.span, n.span)) inside.push(n);
2153
2849
  }
2154
2850
  const maximal = inside.filter(
2155
- (n) => !inside.some((o2) => o2 !== n && o2.span && n.span && strictlyContains(o2.span, n.span))
2851
+ (n) => !inside.some((o2) => o2 !== n && o2.span && n.span && strictlyContains2(o2.span, n.span))
2156
2852
  );
2157
2853
  if (maximal.length !== 1) return;
2158
2854
  const child = maximal[0];
@@ -2161,25 +2857,25 @@ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
2161
2857
  if (extractKeyAttr(sf.text.slice(childOpen.start, childOpen.end))) return;
2162
2858
  ms.appendLeft(childOpen.start + 1 + child.tag.length, ` ${keyAttr}`);
2163
2859
  }
2164
- function surgicalPrint(doc) {
2165
- const sf = primarySource(doc);
2860
+ function surgicalPrint2(doc) {
2861
+ const sf = primarySource2(doc);
2166
2862
  if (!sf) return null;
2167
- const ms = new import_magic_string.default(sf.text);
2168
- const kept = collectKept(doc);
2863
+ const ms = new import_magic_string2.default(sf.text);
2864
+ const kept = collectKept2(doc);
2169
2865
  const keptSpans = [];
2170
2866
  for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
2171
2867
  const removed = [];
2172
- for (const id of backrefIds(doc)) {
2868
+ for (const id of backrefIds2(doc)) {
2173
2869
  if (doc.nodes.has(id)) continue;
2174
2870
  const back = doc.backref.get(id);
2175
2871
  if (!back || back.span.file !== sf.id) continue;
2176
- const unwrapped = keptSpans.some((k3) => strictlyContains(back.span, k3));
2872
+ const unwrapped = keptSpans.some((k3) => strictlyContains2(back.span, k3));
2177
2873
  removed.push({ backref: back, unwrapped });
2178
2874
  }
2179
2875
  const fullRemovals = removed.filter((r2) => !r2.unwrapped).map((r2) => r2.backref.span);
2180
2876
  for (const r2 of removed) {
2181
- const span = r2.backref.span;
2182
- const coveredByFull = fullRemovals.some((f) => f !== span && strictlyContains(f, span));
2877
+ const span2 = r2.backref.span;
2878
+ const coveredByFull = fullRemovals.some((f) => f !== span2 && strictlyContains2(f, span2));
2183
2879
  if (coveredByFull) continue;
2184
2880
  if (r2.unwrapped) {
2185
2881
  transferKeyOnUnwrap(ms, doc, sf, r2.backref, kept);
@@ -2190,15 +2886,15 @@ function surgicalPrint(doc) {
2190
2886
  ms.remove(close.start, close.end);
2191
2887
  }
2192
2888
  } else {
2193
- ms.remove(span.start, span.end);
2889
+ ms.remove(span2.start, span2.end);
2194
2890
  }
2195
2891
  }
2196
2892
  for (const n of kept) {
2197
- if (n.kind === "element") editClasses(ms, doc, sf, n);
2893
+ if (n.kind === "element") editClasses2(ms, doc, sf, n);
2198
2894
  }
2199
2895
  return ms.toString();
2200
2896
  }
2201
- function backrefIds(doc) {
2897
+ function backrefIds2(doc) {
2202
2898
  const out = [];
2203
2899
  const max = doc.alloc.peek;
2204
2900
  for (let i = 1; i < max; i += 1) {
@@ -2213,7 +2909,7 @@ function classText(doc, classes) {
2213
2909
  if (dynamic && dynamic.kind === "dynamic") {
2214
2910
  return `className={${exprText(doc, dynamic.expr).text}}`;
2215
2911
  }
2216
- const tokens = staticTokensOf2(classes);
2912
+ const tokens = staticTokensOf3(classes);
2217
2913
  return `className="${tokens.join(" ")}"`;
2218
2914
  }
2219
2915
  function attrText(doc, name, value) {
@@ -2266,15 +2962,15 @@ function rePrint(doc) {
2266
2962
  if (!root || root.kind !== "fragment") return printNode(doc, doc.root);
2267
2963
  return root.children.map((c) => printNode(doc, c)).join("");
2268
2964
  }
2269
- function doPrint(doc) {
2270
- const surgical = surgicalPrint(doc);
2965
+ function doPrint2(doc) {
2966
+ const surgical = surgicalPrint2(doc);
2271
2967
  return surgical ?? rePrint(doc);
2272
2968
  }
2273
2969
  var jsxBackend = {
2274
2970
  name: "babel-jsx",
2275
2971
  langs: JSX_LANGS2,
2276
2972
  print(doc, _plan, _ctx) {
2277
- const code = doPrint(doc);
2973
+ const code = doPrint2(doc);
2278
2974
  return { code, map: null, edits: [], diagnostics: [] };
2279
2975
  }
2280
2976
  };
@@ -2994,19 +3690,28 @@ var flexCenterWrapper = definePattern({
2994
3690
  flattenInto: "child",
2995
3691
  childGains: { placeSelf: "center" }
2996
3692
  },
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 REVERTSso 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.
3693
+ // Collapsing a flex-centering wrapper to `place-self:center` on the child is render-identical ONLY
3694
+ // when the child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there
3695
+ // `place-self`'s align-self AND justify-self both take effect). Under that ONE context the flatten is
3696
+ // classified `provably-safe` and commits; under a flex/block/unknown parentor when the wrapper
3697
+ // drops any own style it stays `needs-verification` and the conservative production gate PRESERVES
3698
+ // it. Op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is additionally
3699
+ // asserted by the invariant suite over every pattern.
3003
3700
  test: {
3701
+ cases: [
3702
+ {
3703
+ name: "grid parent \u2192 flattened (child gains place-self-center)",
3704
+ before: '<div className="grid"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
3705
+ after: '<div className="grid"><span className="bg-red-200 place-self-center">x</span></div>'
3706
+ }
3707
+ ],
3004
3708
  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.
3709
+ // Non-grid (flex) parent (document root): `justify-self` is ignored in flex → not provably safe.
3009
3710
  '<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
3711
+ // Grid parent, but the wrapper drops padding when removed → not layout-neutral (rule 3).
3712
+ '<div className="grid"><div className="p-4 flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
3713
+ // Grid parent forcing place-items-center: the wrapper would not fill its area → fill guard skips.
3714
+ '<div className="grid place-items-center"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
3010
3715
  // onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
3011
3716
  '<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
3012
3717
  ]
@@ -4226,11 +4931,11 @@ var paddingShorthand = definePattern({
4226
4931
 
4227
4932
  // ../patterns/src/library/compress/place-shorthand.pattern.ts
4228
4933
  init_cjs_shims();
4229
- var ALIGN_ITEMS = "align-items";
4230
- var JUSTIFY_ITEMS = "justify-items";
4231
- var PLACE_ITEMS = "place-items";
4934
+ var ALIGN_ITEMS2 = "align-items";
4935
+ var JUSTIFY_ITEMS2 = "justify-items";
4936
+ var PLACE_ITEMS2 = "place-items";
4232
4937
  var ALIGN_CONTENT = "align-content";
4233
- var JUSTIFY_CONTENT = "justify-content";
4938
+ var JUSTIFY_CONTENT2 = "justify-content";
4234
4939
  var PLACE_CONTENT = "place-content";
4235
4940
  var BASE_KEY8 = conditionKey(BASE_CONDITION);
4236
4941
  function samePair(a, b3) {
@@ -4270,21 +4975,21 @@ var placeShorthand = definePattern({
4270
4975
  rewriteClasses(computed2) {
4271
4976
  const base = computed2.blocks.get(BASE_KEY8);
4272
4977
  if (!base) return null;
4273
- const alignItems = base.decls.get(ALIGN_ITEMS);
4274
- const justifyItems = base.decls.get(JUSTIFY_ITEMS);
4978
+ const alignItems = base.decls.get(ALIGN_ITEMS2);
4979
+ const justifyItems = base.decls.get(JUSTIFY_ITEMS2);
4275
4980
  const alignContent = base.decls.get(ALIGN_CONTENT);
4276
- const justifyContent = base.decls.get(JUSTIFY_CONTENT);
4981
+ const justifyContent = base.decls.get(JUSTIFY_CONTENT2);
4277
4982
  const next = new Map(base.decls);
4278
4983
  let collapsed = false;
4279
4984
  if (samePair(alignItems, justifyItems)) {
4280
- next.delete(ALIGN_ITEMS);
4281
- next.delete(JUSTIFY_ITEMS);
4282
- next.set(PLACE_ITEMS, placeDecl(PLACE_ITEMS, alignItems));
4985
+ next.delete(ALIGN_ITEMS2);
4986
+ next.delete(JUSTIFY_ITEMS2);
4987
+ next.set(PLACE_ITEMS2, placeDecl(PLACE_ITEMS2, alignItems));
4283
4988
  collapsed = true;
4284
4989
  }
4285
4990
  if (samePair(alignContent, justifyContent)) {
4286
4991
  next.delete(ALIGN_CONTENT);
4287
- next.delete(JUSTIFY_CONTENT);
4992
+ next.delete(JUSTIFY_CONTENT2);
4288
4993
  next.set(PLACE_CONTENT, placeDecl(PLACE_CONTENT, alignContent));
4289
4994
  collapsed = true;
4290
4995
  }
@@ -4621,19 +5326,19 @@ init_cjs_shims();
4621
5326
 
4622
5327
  // ../resolver-css/src/engine.ts
4623
5328
  init_cjs_shims();
4624
- var import_node_module = require("module");
4625
- var path2 = __toESM(require("path"), 1);
5329
+ var import_node_module2 = require("module");
5330
+ var path3 = __toESM(require("path"), 1);
4626
5331
  function moduleBase() {
4627
5332
  return typeof __filename === "string" ? __filename : importMetaUrl;
4628
5333
  }
4629
5334
  function loadPostcssEngine(projectRoot) {
4630
5335
  const bases = [];
4631
- if (projectRoot) bases.push(path2.join(projectRoot, "__domflax__.js"));
4632
- bases.push(path2.join(process.cwd(), "__domflax__.js"));
5336
+ if (projectRoot) bases.push(path3.join(projectRoot, "__domflax__.js"));
5337
+ bases.push(path3.join(process.cwd(), "__domflax__.js"));
4633
5338
  bases.push(moduleBase());
4634
5339
  for (const base of bases) {
4635
5340
  try {
4636
- const req = (0, import_node_module.createRequire)(base);
5341
+ const req = (0, import_node_module2.createRequire)(base);
4637
5342
  req.resolve("postcss");
4638
5343
  req.resolve("postcss-selector-parser");
4639
5344
  const postcss = req("postcss");
@@ -4684,15 +5389,15 @@ function collectDecls(rule) {
4684
5389
 
4685
5390
  // ../resolver-css/src/misc-helpers.ts
4686
5391
  init_cjs_shims();
4687
- var import_node_fs = require("fs");
5392
+ var import_node_fs2 = require("fs");
4688
5393
  function isPlainClassToken(token) {
4689
5394
  return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
4690
5395
  }
4691
- function readCssPath(path7) {
5396
+ function readCssPath(path10) {
4692
5397
  try {
4693
- return { id: path7, css: (0, import_node_fs.readFileSync)(path7, "utf8") };
5398
+ return { id: path10, css: (0, import_node_fs2.readFileSync)(path10, "utf8") };
4694
5399
  } catch (cause) {
4695
- throw new Error(`resolver-css: cannot read CSS file "${path7}"`, { cause });
5400
+ throw new Error(`resolver-css: cannot read CSS file "${path10}"`, { cause });
4696
5401
  }
4697
5402
  }
4698
5403
  function deriveFingerprint(provider, files) {
@@ -5070,19 +5775,19 @@ function synthesizeResidual(remaining, ctx) {
5070
5775
 
5071
5776
  // ../resolver-tailwind/src/tailwind/engine.ts
5072
5777
  init_cjs_shims();
5073
- var import_node_module2 = require("module");
5074
- var path3 = __toESM(require("path"), 1);
5778
+ var import_node_module3 = require("module");
5779
+ var path4 = __toESM(require("path"), 1);
5075
5780
  function moduleBase2() {
5076
5781
  return typeof __filename === "string" ? __filename : importMetaUrl;
5077
5782
  }
5078
5783
  function projectRequire(projectRoot) {
5079
5784
  const bases = [];
5080
- if (projectRoot) bases.push(path3.join(projectRoot, "__domflax__.js"));
5081
- bases.push(path3.join(process.cwd(), "__domflax__.js"));
5785
+ if (projectRoot) bases.push(path4.join(projectRoot, "__domflax__.js"));
5786
+ bases.push(path4.join(process.cwd(), "__domflax__.js"));
5082
5787
  bases.push(moduleBase2());
5083
5788
  for (const base of bases) {
5084
5789
  try {
5085
- const candidate = (0, import_node_module2.createRequire)(base);
5790
+ const candidate = (0, import_node_module3.createRequire)(base);
5086
5791
  candidate.resolve("tailwindcss/package.json");
5087
5792
  return candidate;
5088
5793
  } catch {
@@ -5501,6 +6206,74 @@ function createTailwindResolver(config) {
5501
6206
  return new TailwindResolver(config);
5502
6207
  }
5503
6208
 
6209
+ // ../cli/src/transform.ts
6210
+ var path6 = __toESM(require("path"), 1);
6211
+
6212
+ // ../cli/src/html-css.ts
6213
+ init_cjs_shims();
6214
+ var import_node_crypto = require("crypto");
6215
+ var import_node_fs3 = require("fs");
6216
+ var path5 = __toESM(require("path"), 1);
6217
+ function isRemoteHref(href) {
6218
+ const h2 = href.trim();
6219
+ return h2.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(h2);
6220
+ }
6221
+ function attrValue(tag, name) {
6222
+ const re = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>]+))`, "i");
6223
+ const m2 = re.exec(tag);
6224
+ if (!m2) return null;
6225
+ return (m2[1] ?? m2[2] ?? m2[3] ?? "").trim();
6226
+ }
6227
+ function extractLinkHrefs(html) {
6228
+ const out = [];
6229
+ const linkRe = /<link\b[^>]*>/gi;
6230
+ let m2;
6231
+ while ((m2 = linkRe.exec(html)) !== null) {
6232
+ const tag = m2[0];
6233
+ const rel = attrValue(tag, "rel");
6234
+ if (rel === null || !/(?:^|\s)stylesheet(?:\s|$)/i.test(rel)) continue;
6235
+ const href = attrValue(tag, "href");
6236
+ if (href) out.push(href);
6237
+ }
6238
+ return out;
6239
+ }
6240
+ function extractInlineStyles(html) {
6241
+ const out = [];
6242
+ const styleRe = /<style\b([^>]*)>([\s\S]*?)<\/style>/gi;
6243
+ let m2;
6244
+ while ((m2 = styleRe.exec(html)) !== null) {
6245
+ const type = attrValue(`<style ${m2[1] ?? ""}>`, "type")?.toLowerCase();
6246
+ if (type && type !== "text/css" && type !== "css") continue;
6247
+ const css = m2[2] ?? "";
6248
+ if (css.trim().length > 0) out.push(css);
6249
+ }
6250
+ return out;
6251
+ }
6252
+ function extractHtmlStylesheets(htmlCode, htmlAbsPath) {
6253
+ const dir = path5.dirname(path5.resolve(htmlAbsPath));
6254
+ const files = [];
6255
+ const seen = /* @__PURE__ */ new Set();
6256
+ for (const rawHref of extractLinkHrefs(htmlCode)) {
6257
+ const href = (rawHref.split(/[?#]/, 1)[0] ?? "").trim();
6258
+ if (!href || isRemoteHref(href)) continue;
6259
+ const abs = path5.resolve(dir, href);
6260
+ if (seen.has(abs)) continue;
6261
+ seen.add(abs);
6262
+ if ((0, import_node_fs3.existsSync)(abs)) files.push(abs);
6263
+ }
6264
+ return { files, inline: extractInlineStyles(htmlCode) };
6265
+ }
6266
+ function cssSetKey(sortedResolvedPaths, inline) {
6267
+ const h2 = (0, import_node_crypto.createHash)("sha1");
6268
+ h2.update(sortedResolvedPaths.join("\n"));
6269
+ h2.update("\0inline\0");
6270
+ for (const block of inline) {
6271
+ h2.update(block);
6272
+ h2.update("\0");
6273
+ }
6274
+ return h2.digest("hex");
6275
+ }
6276
+
5504
6277
  // ../cli/src/transform.ts
5505
6278
  function buildResolver(provider, css, projectRoot) {
5506
6279
  if (provider === "custom") {
@@ -5537,6 +6310,11 @@ function jsxKindOf(id) {
5537
6310
  if (lower.endsWith(".jsx")) return "jsx";
5538
6311
  return null;
5539
6312
  }
6313
+ function htmlKindOf(id) {
6314
+ const lower = (id.split("?", 1)[0] ?? id).toLowerCase();
6315
+ if (lower.endsWith(".html") || lower.endsWith(".htm")) return "html";
6316
+ return null;
6317
+ }
5540
6318
  function countClassTokens(code) {
5541
6319
  let total = 0;
5542
6320
  const re = /\b(?:className|class)\s*=\s*"([^"]*)"/g;
@@ -5569,9 +6347,25 @@ function passthroughResult(code) {
5569
6347
  }
5570
6348
  function createTransform(options) {
5571
6349
  const projectRoot = options.projectRoot ?? process.cwd();
5572
- const resolver = buildResolver(options.provider, options.css, projectRoot);
6350
+ const globalResolver = buildResolver(options.provider, options.css, projectRoot);
5573
6351
  const patterns = selectPatterns(options.passes);
5574
- function prepare(code, id, kind, gate) {
6352
+ const resolverCache = /* @__PURE__ */ new Map();
6353
+ function resolverFor(code, id) {
6354
+ if (options.provider !== "custom" || htmlKindOf(id) === null) return globalResolver;
6355
+ const { files: localFiles, inline } = extractHtmlStylesheets(code, id);
6356
+ if (localFiles.length === 0 && inline.length === 0) return globalResolver;
6357
+ const globalPaths = options.css.map((p2) => path6.resolve(p2));
6358
+ const sortedPaths = [.../* @__PURE__ */ new Set([...globalPaths, ...localFiles])].sort();
6359
+ const key = cssSetKey(sortedPaths, inline);
6360
+ let resolver = resolverCache.get(key);
6361
+ if (!resolver) {
6362
+ const inlineFiles = inline.map((css, i) => ({ id: `${id}#inline-${i}`, css }));
6363
+ resolver = createCssResolver(inlineFiles, { files: sortedPaths, projectRoot });
6364
+ resolverCache.set(key, resolver);
6365
+ }
6366
+ return resolver;
6367
+ }
6368
+ function prepare(code, id, kind, gate, resolver) {
5575
6369
  const parsed = createJsxFrontend().parse(code, {
5576
6370
  id,
5577
6371
  kind,
@@ -5596,9 +6390,32 @@ function createTransform(options) {
5596
6390
  };
5597
6391
  return { doc, ctx, passes: buildPasses(patterns), nodesIn };
5598
6392
  }
5599
- function finish(code, optimized, id, nodesIn) {
6393
+ function prepareHtml(code, id, gate, resolver) {
6394
+ const parsed = createHtmlFrontend().parse(code, {
6395
+ id,
6396
+ kind: "html",
6397
+ resolver,
6398
+ normalizer,
6399
+ config: {},
6400
+ onDiagnostic: () => {
6401
+ }
6402
+ });
6403
+ const doc = parsed.doc;
6404
+ const nodesIn = doc.nodes.size;
6405
+ const ctx = {
6406
+ doc,
6407
+ safetyCeiling: options.safety,
6408
+ normalizer,
6409
+ selectors: buildSelectorIndex(doc, resolver),
6410
+ resolver,
6411
+ gate
6412
+ };
6413
+ return { doc, ctx, passes: buildPasses(patterns), nodesIn };
6414
+ }
6415
+ function finish(code, optimized, id, nodesIn, resolver, backend = "jsx") {
5600
6416
  syncClassesFromComputed(optimized, resolver, normalizer);
5601
- const printed = createJsxBackend().print(
6417
+ const print = backend === "html" ? createHtmlBackend().print : createJsxBackend().print;
6418
+ const printed = print(
5602
6419
  optimized,
5603
6420
  { moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
5604
6421
  { normalizer, resolver, sink: createSyntheticSink(), eol: "\n", onDiagnostic: () => {
@@ -5626,24 +6443,30 @@ function createTransform(options) {
5626
6443
  };
5627
6444
  }
5628
6445
  return {
5629
- resolver,
6446
+ resolver: globalResolver,
5630
6447
  transformFile(code, id) {
5631
6448
  const kind = jsxKindOf(id);
5632
- if (kind === null) return passthroughResult(code);
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);
6449
+ if (kind !== null) {
6450
+ const resolver = resolverFor(code, id);
6451
+ const { doc, ctx, passes, nodesIn } = prepare(code, id, kind, "provably-safe", resolver);
6452
+ const { doc: optimized } = runPasses(doc, passes, ctx);
6453
+ return finish(code, optimized, id, nodesIn, resolver);
6454
+ }
6455
+ if (htmlKindOf(id) !== null) {
6456
+ const resolver = resolverFor(code, id);
6457
+ const { doc, ctx, passes, nodesIn } = prepareHtml(code, id, "provably-safe", resolver);
6458
+ const { doc: optimized } = runPasses(doc, passes, ctx);
6459
+ return finish(code, optimized, id, nodesIn, resolver, "html");
6460
+ }
6461
+ return passthroughResult(code);
5636
6462
  }
5637
6463
  };
5638
6464
  }
5639
- function builtinPatternNames() {
5640
- return builtinPatterns.map((p2) => p2.name);
5641
- }
5642
6465
 
5643
6466
  // ../cli/src/walk.ts
5644
6467
  init_cjs_shims();
5645
6468
  var fs = __toESM(require("fs"), 1);
5646
- var path4 = __toESM(require("path"), 1);
6469
+ var path7 = __toESM(require("path"), 1);
5647
6470
  var SUPPORTED_EXTS = [".jsx", ".tsx", ".html", ".htm"];
5648
6471
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "domflax-out"]);
5649
6472
  function isSupported(file) {
@@ -5661,12 +6484,12 @@ function walkDir(dir, out) {
5661
6484
  return;
5662
6485
  }
5663
6486
  for (const entry of entries) {
5664
- const full = path4.join(dir, entry.name);
6487
+ const full = path7.join(dir, entry.name);
5665
6488
  if (entry.isDirectory()) {
5666
6489
  if (SKIP_DIRS.has(entry.name)) continue;
5667
6490
  walkDir(full, out);
5668
- } else if (entry.isFile() && isSupported(entry.name)) {
5669
- out.push(full);
6491
+ } else if (entry.isFile()) {
6492
+ if (isSupported(entry.name)) out.push(full);
5670
6493
  }
5671
6494
  }
5672
6495
  }
@@ -5679,7 +6502,7 @@ function discoverInputs(paths) {
5679
6502
  const warnings = [];
5680
6503
  const seen = /* @__PURE__ */ new Set();
5681
6504
  const push = (f) => {
5682
- const abs = path4.resolve(f);
6505
+ const abs = path7.resolve(f);
5683
6506
  if (!seen.has(abs)) {
5684
6507
  seen.add(abs);
5685
6508
  files.push(abs);
@@ -5688,7 +6511,7 @@ function discoverInputs(paths) {
5688
6511
  let inputRoot = process.cwd();
5689
6512
  if (paths.length === 1) {
5690
6513
  try {
5691
- if (fs.statSync(paths[0]).isDirectory()) inputRoot = path4.resolve(paths[0]);
6514
+ if (fs.statSync(paths[0]).isDirectory()) inputRoot = path7.resolve(paths[0]);
5692
6515
  } catch {
5693
6516
  }
5694
6517
  }
@@ -5700,11 +6523,12 @@ function discoverInputs(paths) {
5700
6523
  stat = null;
5701
6524
  }
5702
6525
  if (stat?.isDirectory()) {
5703
- walkDir(path4.resolve(p2), files);
6526
+ walkDir(path7.resolve(p2), files);
5704
6527
  continue;
5705
6528
  }
5706
6529
  if (stat?.isFile()) {
5707
- push(p2);
6530
+ if (isSupported(p2)) push(p2);
6531
+ else warnings.push(`unsupported file type, skipped: ${p2}`);
5708
6532
  continue;
5709
6533
  }
5710
6534
  if (hasGlobMagic(p2)) {
@@ -5713,9 +6537,10 @@ function discoverInputs(paths) {
5713
6537
  warnings.push(`glob not supported on this Node version, skipped: ${p2}`);
5714
6538
  continue;
5715
6539
  }
5716
- const matches = glob(p2).filter(isSupported);
5717
- if (matches.length === 0) warnings.push(`no files matched: ${p2}`);
5718
- for (const m2 of matches) push(m2);
6540
+ const matched = glob(p2);
6541
+ const supported = matched.filter(isSupported);
6542
+ if (supported.length === 0) warnings.push(`no .jsx/.tsx/.html files matched: ${p2}`);
6543
+ for (const m2 of supported) push(m2);
5719
6544
  continue;
5720
6545
  }
5721
6546
  warnings.push(`no such file or directory: ${p2}`);
@@ -5723,7 +6548,7 @@ function discoverInputs(paths) {
5723
6548
  const deduped = [];
5724
6549
  const finalSeen = /* @__PURE__ */ new Set();
5725
6550
  for (const f of files) {
5726
- const abs = path4.resolve(f);
6551
+ const abs = path7.resolve(f);
5727
6552
  if (!finalSeen.has(abs)) {
5728
6553
  finalSeen.add(abs);
5729
6554
  deduped.push(abs);
@@ -6316,7 +7141,7 @@ var J2 = `${import_picocolors2.default.gray(o)} `;
6316
7141
  // ../cli/src/detect.ts
6317
7142
  init_cjs_shims();
6318
7143
  var fs2 = __toESM(require("fs"), 1);
6319
- var path5 = __toESM(require("path"), 1);
7144
+ var path8 = __toESM(require("path"), 1);
6320
7145
  var SKIP_DIRS2 = /* @__PURE__ */ new Set([
6321
7146
  "node_modules",
6322
7147
  "dist",
@@ -6329,15 +7154,17 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
6329
7154
  ]);
6330
7155
  var COMMON_INPUT_DIRS = ["src", "app", "components", "pages", "lib", "ui", "public"];
6331
7156
  var CSS_FILE_CAP = 200;
7157
+ var DEFAULT_CSS_DEPTH = 10;
6332
7158
  function toRelative(root, abs) {
6333
- const rel = path5.relative(root, abs);
6334
- return rel.split(path5.sep).join("/");
7159
+ const rel = path8.relative(root, abs);
7160
+ return rel.split(path8.sep).join("/");
6335
7161
  }
6336
- function detectCssFiles(root) {
6337
- const found = [];
7162
+ function detectCssFiles(root, scanRoots = [], maxDepth = DEFAULT_CSS_DEPTH) {
7163
+ const base = path8.resolve(root);
7164
+ const found = /* @__PURE__ */ new Map();
6338
7165
  let capped = false;
6339
- const walk = (dir) => {
6340
- if (capped) return;
7166
+ const walk = (dir, depth) => {
7167
+ if (capped || depth > maxDepth) return;
6341
7168
  let entries;
6342
7169
  try {
6343
7170
  entries = fs2.readdirSync(dir, { withFileTypes: true });
@@ -6345,32 +7172,40 @@ function detectCssFiles(root) {
6345
7172
  return;
6346
7173
  }
6347
7174
  for (const entry of entries) {
6348
- const full = path5.join(dir, entry.name);
7175
+ const full = path8.join(dir, entry.name);
6349
7176
  if (entry.isDirectory()) {
6350
7177
  if (SKIP_DIRS2.has(entry.name)) continue;
6351
- walk(full);
7178
+ walk(full, depth + 1);
6352
7179
  if (capped) return;
6353
7180
  } 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;
7181
+ const abs = path8.resolve(full);
7182
+ if (!found.has(abs)) {
7183
+ found.set(abs, toRelative(base, abs));
7184
+ if (found.size >= CSS_FILE_CAP) {
7185
+ capped = true;
7186
+ return;
7187
+ }
6358
7188
  }
6359
7189
  }
6360
7190
  }
6361
7191
  };
6362
- walk(path5.resolve(root));
6363
- found.sort((a, b3) => a.localeCompare(b3));
7192
+ walk(base, 0);
7193
+ for (const r2 of scanRoots) {
7194
+ if (capped) break;
7195
+ const abs = path8.resolve(r2);
7196
+ if (abs !== base) walk(abs, 0);
7197
+ }
7198
+ const list = [...found.values()].sort((a, b3) => a.localeCompare(b3));
6364
7199
  if (capped) {
6365
7200
  console.error(`domflax: more than ${CSS_FILE_CAP} CSS files found; showing the first ${CSS_FILE_CAP}.`);
6366
7201
  }
6367
- return found;
7202
+ return list;
6368
7203
  }
6369
7204
  function detectInputDirs(root) {
6370
- const resolved = path5.resolve(root);
7205
+ const resolved = path8.resolve(root);
6371
7206
  return COMMON_INPUT_DIRS.filter((name) => {
6372
7207
  try {
6373
- return fs2.statSync(path5.join(resolved, name)).isDirectory();
7208
+ return fs2.statSync(path8.join(resolved, name)).isDirectory();
6374
7209
  } catch {
6375
7210
  return false;
6376
7211
  }
@@ -6446,15 +7281,6 @@ async function runWizard(base) {
6446
7281
  } else if (outputMode === "overwrite") {
6447
7282
  dangerouslyOverwriteSource = true;
6448
7283
  }
6449
- const allPasses = builtinPatternNames();
6450
- const passSelection = await fe({
6451
- message: "Which optimization passes should run?",
6452
- options: allPasses.map((name) => ({ value: name, label: name })),
6453
- initialValues: [...allPasses],
6454
- required: true
6455
- });
6456
- if (cancelled(passSelection)) return done();
6457
- const passes = passSelection;
6458
7284
  const provider = await ve({
6459
7285
  message: "How should class names resolve to styles?",
6460
7286
  options: [
@@ -6467,7 +7293,7 @@ async function runWizard(base) {
6467
7293
  if (cancelled(provider)) return done();
6468
7294
  let css = base.css;
6469
7295
  if (provider === "custom") {
6470
- const detectedCss = detectCssFiles(root);
7296
+ const detectedCss = detectCssFiles(root, [inputPath]);
6471
7297
  if (detectedCss.length > 0) {
6472
7298
  const picked = await fe({
6473
7299
  message: "Which CSS files should resolve your classes? (all detected files are pre-selected)",
@@ -6495,7 +7321,7 @@ async function runWizard(base) {
6495
7321
  css,
6496
7322
  dryRun,
6497
7323
  dangerouslyOverwriteSource,
6498
- passes: passes.length === allPasses.length ? null : passes,
7324
+ passes: null,
6499
7325
  safety: base.safety ?? DEFAULT_SAFETY
6500
7326
  };
6501
7327
  function done() {
@@ -6505,13 +7331,6 @@ async function runWizard(base) {
6505
7331
  }
6506
7332
 
6507
7333
  // ../cli/src/index.ts
6508
- function addStats(totals, stats, changed) {
6509
- totals.files += 1;
6510
- if (changed) totals.changed += 1;
6511
- totals.nodesRemoved += stats.nodesRemoved;
6512
- totals.classesSaved += stats.classesSaved;
6513
- totals.bytesSaved += stats.bytesSaved;
6514
- }
6515
7334
  function printReport(totals) {
6516
7335
  console.log("");
6517
7336
  console.log("domflax report");
@@ -6521,28 +7340,13 @@ function printReport(totals) {
6521
7340
  console.log(` classes saved : ${totals.classesSaved}`);
6522
7341
  console.log(` bytes saved : ${totals.bytesSaved}`);
6523
7342
  }
6524
- async function execute(options) {
6525
- const { files, inputRoot, warnings } = discoverInputs(options.paths);
6526
- for (const w2 of warnings) console.error(`domflax: ${w2}`);
6527
- if (files.length === 0) {
6528
- console.error("domflax: no .jsx/.tsx/.html files found for the given paths");
6529
- return { exitCode: 1 };
6530
- }
6531
- const projectRoot = options.projectRoot ?? process.cwd();
6532
- const gitClean = options.dangerouslyOverwriteSource && !options.noGitCheck ? isGitClean(projectRoot) : true;
6533
- const planned = planWrites(options, gitClean);
6534
- if (!planned.ok) {
6535
- console.error(`domflax: ${planned.error}`);
6536
- return { exitCode: 1 };
6537
- }
6538
- const plan = planned.value;
7343
+ function runInline(files, options, inputRoot, plan, totals) {
6539
7344
  const transform = createTransform(options);
6540
- const totals = { files: 0, changed: 0, nodesRemoved: 0, classesSaved: 0, bytesSaved: 0 };
6541
7345
  let failures = 0;
6542
7346
  for (const file of files) {
6543
7347
  let code;
6544
7348
  try {
6545
- code = (0, import_node_fs2.readFileSync)(file, "utf8");
7349
+ code = (0, import_node_fs4.readFileSync)(file, "utf8");
6546
7350
  } catch (err) {
6547
7351
  console.error(`domflax: cannot read ${file}: ${String(err?.message ?? err)}`);
6548
7352
  failures += 1;
@@ -6551,7 +7355,7 @@ async function execute(options) {
6551
7355
  const result = transform.transformFile(code, file);
6552
7356
  addStats(totals, result.stats, result.changed);
6553
7357
  if (options.dryRun) {
6554
- const rel = path6.relative(inputRoot, file) || path6.basename(file);
7358
+ const rel = path9.relative(inputRoot, file) || path9.basename(file);
6555
7359
  if (result.changed) console.log(unifiedDiff(code, result.code, rel));
6556
7360
  else if (!options.report) console.log(` (unchanged) ${rel}`);
6557
7361
  continue;
@@ -6564,16 +7368,67 @@ async function execute(options) {
6564
7368
  continue;
6565
7369
  }
6566
7370
  try {
6567
- (0, import_node_fs2.mkdirSync)(path6.dirname(target.value), { recursive: true });
6568
- (0, import_node_fs2.writeFileSync)(target.value, result.code, "utf8");
6569
- console.log(`domflax: wrote ${path6.relative(process.cwd(), target.value) || target.value}`);
7371
+ (0, import_node_fs4.mkdirSync)(path9.dirname(target.value), { recursive: true });
7372
+ (0, import_node_fs4.writeFileSync)(target.value, result.code, "utf8");
7373
+ console.log(`domflax: wrote ${path9.relative(process.cwd(), target.value) || target.value}`);
6570
7374
  } catch (err) {
6571
7375
  console.error(`domflax: cannot write ${target.value}: ${String(err?.message ?? err)}`);
6572
7376
  failures += 1;
6573
7377
  }
6574
7378
  }
7379
+ return failures;
7380
+ }
7381
+ async function execute(options) {
7382
+ const { files, inputRoot, warnings } = discoverInputs(options.paths);
7383
+ for (const w2 of warnings) console.error(`domflax: ${w2}`);
7384
+ if (files.length === 0) {
7385
+ console.error("domflax: no .jsx/.tsx files found for the given paths");
7386
+ return { exitCode: 1 };
7387
+ }
7388
+ const projectRoot = options.projectRoot ?? process.cwd();
7389
+ const gitClean = options.dangerouslyOverwriteSource && !options.noGitCheck ? isGitClean(projectRoot) : true;
7390
+ const planned = planWrites(options, gitClean);
7391
+ if (!planned.ok) {
7392
+ console.error(`domflax: ${planned.error}`);
7393
+ return { exitCode: 1 };
7394
+ }
7395
+ const plan = planned.value;
7396
+ const poolPlan = computeWorkerCount(options);
7397
+ const usePool = !options.dryRun && shouldUsePool(files.length, poolPlan);
7398
+ const totals = emptyTotals();
7399
+ let failures = 0;
7400
+ if (usePool) {
7401
+ const outcome = await runPool(
7402
+ files,
7403
+ { options, inputRoot, plan },
7404
+ poolPlan
7405
+ // Per-file "wrote" lines are collected and printed in deterministic (sorted) order below.
7406
+ );
7407
+ Object.assign(totals, outcome.totals);
7408
+ failures = outcome.failures;
7409
+ for (const { path: p2, error } of outcome.errors) {
7410
+ console.error(`domflax: failed ${path9.relative(process.cwd(), p2) || p2}: ${error}`);
7411
+ }
7412
+ for (const dest of [...outcome.wrote].sort()) {
7413
+ console.log(`domflax: wrote ${path9.relative(process.cwd(), dest) || dest}`);
7414
+ }
7415
+ } else {
7416
+ failures += runInline(files, options, inputRoot, plan, totals);
7417
+ }
7418
+ if (options.dryRun) {
7419
+ console.log("\ndomflax: dry run \u2014 no files were written.");
7420
+ } else if (totals.changed === 0) {
7421
+ console.log(
7422
+ `
7423
+ domflax: processed ${totals.files} file${totals.files === 1 ? "" : "s"} \u2014 nothing to optimize (0 changed).`
7424
+ );
7425
+ } else {
7426
+ console.log(
7427
+ `
7428
+ domflax: optimized ${totals.changed} of ${totals.files} file${totals.files === 1 ? "" : "s"} (${totals.nodesRemoved} nodes removed, ${totals.classesSaved} classes saved, ${totals.bytesSaved} bytes saved).`
7429
+ );
7430
+ }
6575
7431
  if (options.report) printReport(totals);
6576
- if (options.dryRun) console.log("\ndomflax: dry run \u2014 no files were written.");
6577
7432
  return { exitCode: failures > 0 ? 1 : 0 };
6578
7433
  }
6579
7434
  async function main(argv = process.argv.slice(2)) {