domflax 0.1.4 → 0.2.0

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 (42) hide show
  1. package/README.md +47 -29
  2. package/dist/{chunk-EVENAJYI.js → chunk-EYQXQQQH.js} +3 -3
  3. package/dist/{chunk-3Z5ZWLXX.js → chunk-FPT4EJ6Q.js} +805 -1612
  4. package/dist/chunk-FPT4EJ6Q.js.map +1 -0
  5. package/dist/{chunk-5FWENSD2.js → chunk-JBM3MJRM.js} +149 -10
  6. package/dist/chunk-JBM3MJRM.js.map +1 -0
  7. package/dist/{chunk-H5KTGI3A.js → chunk-TTJEXWAC.js} +172 -5
  8. package/dist/chunk-TTJEXWAC.js.map +1 -0
  9. package/dist/cli.cjs +1032 -1640
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +30 -10
  12. package/dist/cli.js.map +1 -1
  13. package/dist/index.cjs +1116 -1627
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +226 -485
  16. package/dist/index.d.ts +226 -485
  17. package/dist/index.js +16 -36
  18. package/dist/{pattern-CP9_HpVK.d.cts → pattern-DotR_dHs.d.cts} +1 -1
  19. package/dist/pattern-kit.cjs +60 -1
  20. package/dist/pattern-kit.cjs.map +1 -1
  21. package/dist/pattern-kit.d.cts +2 -2
  22. package/dist/pattern-kit.d.ts +2 -2
  23. package/dist/pattern-kit.js +1 -1
  24. package/dist/{pattern-CYgsv-jO.d.ts → pattern-urm5uuwj.d.ts} +1 -1
  25. package/dist/{resolve-ops-Ci7LgYHC.d.ts → resolve-ops-D8aQina5.d.cts} +11 -0
  26. package/dist/{resolve-ops-Ci7LgYHC.d.cts → resolve-ops-D8aQina5.d.ts} +11 -0
  27. package/dist/verify.d.cts +1 -1
  28. package/dist/verify.d.ts +1 -1
  29. package/dist/webpack-loader.cjs +1014 -1578
  30. package/dist/webpack-loader.cjs.map +1 -1
  31. package/dist/webpack-loader.d.cts +8 -2
  32. package/dist/webpack-loader.d.ts +8 -2
  33. package/dist/webpack-loader.js +7 -4
  34. package/dist/webpack-loader.js.map +1 -1
  35. package/dist/worker.cjs +983 -1601
  36. package/dist/worker.cjs.map +1 -1
  37. package/dist/worker.js +3 -3
  38. package/package.json +1 -1
  39. package/dist/chunk-3Z5ZWLXX.js.map +0 -1
  40. package/dist/chunk-5FWENSD2.js.map +0 -1
  41. package/dist/chunk-H5KTGI3A.js.map +0 -1
  42. /package/dist/{chunk-EVENAJYI.js.map → chunk-EYQXQQQH.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { e as IRDocument, d as RewriteOp, I as IRNodeId, h as StyleResolver, g as SelectorIndex, f as SafetyLevel, l as PassPhase, r as RewriteFactory, i as Diagnostic, c as StyleNormalizer, R as RewriteOpDraft, j as SyntheticSink } from './resolve-ops-Ci7LgYHC.cjs';
2
- export { A as AttrMap, Q as AttrValue, y as Backref, B as BackrefTable, T as Brand, w as ClassList, U as ClassListForm, W as ClassSegment, X as ClassToken, Y as CommentSpec, u as ConditionKey, C as CssProperty, Z as CssValue, _ as DeclSignature, o as DeepReadonly, D as DiagnosticCode, $ as DistributiveOmit, E as ElementLike, a0 as ElementSpec, a1 as EmitContext, a2 as EmitResult, a3 as ExprKind, a4 as ExprRecord, H as ExprRef, K as ExprRegistry, a5 as ExprSpec, F as FileKind, a6 as FragmentSpec, G as FrontendKind, z as IRComment, p as IRElement, J as IRExpr, L as IRFragment, v as IRNamespace, q as IRNode, a7 as IRNodeBase, a8 as IRNodeKind, O as IRText, M as IdAllocator, a9 as InheritedPropertyTable, x as InlineStyle, N as NodeLike, n as NodeMeta, aa as NodeRefSpec, b as NodeSpec, ab as OpOrigin, ac as OpaqueReason, ad as OpaqueToken, m as PassCategory, ae as PassTraceEntry, P as PatternName, af as Position, s as Reporter, ag as ResolveInput, ah as ResolveResult, ai as ResolverDiagnostic, aj as SelectorUsage, ak as Severity, al as SourceFile, am as SourceFileId, k as SourceSpan, an as StyleBlock, t as StyleCondition, a as StyleConflictPolicy, ao as StyleDecl, S as StyleMap, ap as StyleOrigin, aq as SyntheticClass, ar as TextSpec, as as VisitContext, at as VisitSignal, V as Visitor } from './resolve-ops-Ci7LgYHC.cjs';
3
- import { y as ApplyResult, z as RewriteGroup, B as ApplyContext, C as MatchContext, E as FixpointConfig, H as HaltReason, G as Pass, I as PassManager, J as Pattern, K as PhaseRunResult, L as Pipeline, A as AuthoredPattern, N as Captures, O as EncodedSourceMap } from './pattern-CP9_HpVK.cjs';
4
- export { Q as AppliedOp, S as BASE_CONDITION, U as BASE_CONDITION_KEY, V as Backend, W as BackendContext, X as CodegenResult, Y as EditPlan, Z as ElementInit, _ as FlattenGate, $ as Frontend, a0 as FrontendConfig, a1 as FrontendParseContext, a2 as MatchResult, a3 as MutableBackrefTable, a4 as OpValidationIssue, a5 as ParentLayoutContext, a6 as ParseResult, a7 as PatternDoc, a8 as PipelineConfig, a9 as PipelineInput, aa as PipelineOutput, ab as PipelineStats, ac as PreconditionSketch, ad as ReindentSpec, ae as RewriteContext, af as SkippedOpGroup, ag as StructuralInverse, ah as StylePredicate, ai as TextEdit, aj as TreeShapeSketch, ak as childIds, al as conditionKey, am as createBackrefTable, an as createComment, ao as createDocument, ap as createElement, aq as createExpr, ar as createExprRegistry, as as createFragment, at as createIdAllocator, au as createText, av as defaultMeta, aw as elementIds, ax as emptyAttrMap, ay as emptyClassList, az as emptyInlineStyle, l as emptyStyleMap, aA as getElement, aB as getNode, aC as walk } from './pattern-CP9_HpVK.cjs';
1
+ import { e as IRDocument, d as RewriteOp, I as IRNodeId, h as StyleResolver, g as SelectorIndex, f as SafetyLevel, l as PassPhase, r as RewriteFactory, i as Diagnostic, c as StyleNormalizer, R as RewriteOpDraft, j as SyntheticSink, S as StyleMap } from './resolve-ops-D8aQina5.cjs';
2
+ export { A as AttrMap, Q as AttrValue, y as Backref, B as BackrefTable, T as Brand, w as ClassList, U as ClassListForm, W as ClassSegment, X as ClassToken, Y as CommentSpec, u as ConditionKey, C as CssProperty, Z as CssValue, _ as DeclSignature, o as DeepReadonly, D as DiagnosticCode, $ as DistributiveOmit, E as ElementLike, a0 as ElementSpec, a1 as EmitContext, a2 as EmitResult, a3 as ExprKind, a4 as ExprRecord, H as ExprRef, K as ExprRegistry, a5 as ExprSpec, F as FileKind, a6 as FragmentSpec, G as FrontendKind, z as IRComment, p as IRElement, J as IRExpr, L as IRFragment, v as IRNamespace, q as IRNode, a7 as IRNodeBase, a8 as IRNodeKind, O as IRText, M as IdAllocator, a9 as InheritedPropertyTable, x as InlineStyle, N as NodeLike, n as NodeMeta, aa as NodeRefSpec, b as NodeSpec, ab as OpOrigin, ac as OpaqueReason, ad as OpaqueToken, m as PassCategory, ae as PassTraceEntry, P as PatternName, af as Position, s as Reporter, ag as ResolveInput, ah as ResolveResult, ai as ResolverDiagnostic, aj as SelectorUsage, ak as Severity, al as SourceFile, am as SourceFileId, k as SourceSpan, an as StyleBlock, t as StyleCondition, a as StyleConflictPolicy, ao as StyleDecl, ap as StyleOrigin, aq as SyntheticClass, ar as TextSpec, as as VisitContext, at as VisitSignal, V as Visitor } from './resolve-ops-D8aQina5.cjs';
3
+ import { y as ApplyResult, z as RewriteGroup, B as ApplyContext, C as MatchContext, E as FixpointConfig, H as HaltReason, G as Pass, I as PassManager, J as Pattern, K as PhaseRunResult, L as Pipeline, A as AuthoredPattern, N as Captures, O as EncodedSourceMap } from './pattern-DotR_dHs.cjs';
4
+ export { Q as AppliedOp, S as BASE_CONDITION, U as BASE_CONDITION_KEY, V as Backend, W as BackendContext, X as CodegenResult, Y as EditPlan, Z as ElementInit, _ as FlattenGate, $ as Frontend, a0 as FrontendConfig, a1 as FrontendParseContext, a2 as MatchResult, a3 as MutableBackrefTable, a4 as OpValidationIssue, a5 as ParentLayoutContext, a6 as ParseResult, a7 as PatternDoc, a8 as PipelineConfig, a9 as PipelineInput, aa as PipelineOutput, ab as PipelineStats, ac as PreconditionSketch, ad as ReindentSpec, ae as RewriteContext, af as SkippedOpGroup, ag as StructuralInverse, ah as StylePredicate, ai as TextEdit, aj as TreeShapeSketch, ak as childIds, al as conditionKey, am as createBackrefTable, an as createComment, ao as createDocument, ap as createElement, aq as createExpr, ar as createExprRegistry, as as createFragment, at as createIdAllocator, au as createText, av as defaultMeta, aw as elementIds, ax as emptyAttrMap, ay as emptyClassList, az as emptyInlineStyle, l as emptyStyleMap, aA as getElement, aB as getNode, aC as walk } from './pattern-DotR_dHs.cjs';
5
5
 
6
6
  /**
7
7
  * @domflax/core — applier runtime helpers (shared by the per-op handlers in `./apply`).
@@ -237,65 +237,121 @@ declare function createPipeline(): Pipeline;
237
237
  */
238
238
 
239
239
  /**
240
- * Fold every STYLE-DIRTY, rewritable element's optimized computed style back into the MINIMAL static
241
- * class-token set (see module docs). Mutates `doc` in place.
240
+ * Fold each rewritable element's computed style back into the MINIMAL static class-token set — the
241
+ * general compress engine (see module docs + {@link import('./compress-engine')}). Mutates `doc`.
242
+ *
243
+ * TWO kinds of element are processed, and their guarantees differ:
244
+ *
245
+ * • STYLE-DIRTY — a pass rewrote this element's own computed style (a flatten fold / merge). Its
246
+ * computed CHANGED, so its classes MUST be re-derived to represent the new style (which may
247
+ * legitimately need MORE tokens than before). Handled exactly as it always was.
248
+ *
249
+ * • COMPRESS-ONLY — no pass touched it; we run the exact-cover engine purely to SHORTEN its class
250
+ * string (`px-4 py-4 → p-4`, drop a redundant class, pick a custom class that covers the same
251
+ * style, …). This is a pure class-string rewrite that must NEVER change the render or GROW the
252
+ * output, so it carries two extra hard backstops below: the rewritten set must re-resolve to the
253
+ * element's exact computed style, and it must not be longer than the original. A structural
254
+ * bystander with no compression opportunity therefore keeps its `class` attribute byte-for-byte.
242
255
  */
243
256
  declare function syncClassesFromComputed(doc: IRDocument, resolver: StyleResolver, norm: StyleNormalizer): void;
244
257
 
245
258
  /**
246
- * @domflax/patternsflatten pattern: `display-contents-wrapper`.
259
+ * @domflax/corethe general COMPRESS ENGINE: a minimal-string exact-cover solver.
247
260
  *
248
- * Collapses a wrapper that has explicitly opted OUT of generating a box:
261
+ * ## What it replaces
249
262
  *
250
- * <div style="display:contents"><Child/></div> → <Child/>
263
+ * The hand-written compress patterns (padding/margin/inset/size/gap/place/border/overflow/…-shorthand
264
+ * and dedupe-classes) each recognised ONE shorthand shape and folded it. This module subsumes them
265
+ * ALL with a SINGLE, provider-uniform algorithm: given an element's target computed style, find the
266
+ * class set that reproduces it EXACTLY with the SHORTEST total `class="…"` string, searching the whole
267
+ * vocabulary (every Tailwind utility AND the project's custom-CSS classes at once).
251
268
  *
252
- * `display:contents` makes an element generate NO box of its own its children render exactly as if
253
- * they were direct children of the element's parent. A `display:contents` wrapper with a single
254
- * element child is therefore already a layout passthrough: it contributes nothing to flow, paint
255
- * (a contents box paints nothing), formatting, stacking, or containing-block resolution. Removing it
256
- * and hoisting the child produces a tree that is layout-identical — the wrapper's only remaining
257
- * effect was inheritance, which is preserved by folding inheritable declarations onto the child first.
269
+ * ## The algorithm (per element, per style-conditionall folded into one solve)
258
270
  *
259
- * This is the safest possible wrapper-elimination: the box being removed provably did not exist.
260
- * The opacity-barrier + selector-safety guards (ref/handlers/dynamic-children/raw-html/combinator/
261
- * reparent-impact) are auto-applied for every `flatten/*` pattern; the `where` predicates add the
262
- * passthrough-specific requirements (no own attrs / dynamic-or-spread classes, no `var()` coupling,
263
- * not a component, not a structural-pseudo subject).
271
+ * Given a normalized target StyleMap `U`:
272
+ * 1. UNIVERSE = the set of `(conditionKey, property, value, important)` tuples in `U`
273
+ * (a `tupleKey` per {@link tupleKey}).
274
+ * 2. CANDIDATES = the vocabulary classes whose FULL normalized-longhand declaration set is a SUBSET
275
+ * of `U` (never introduces a declaration `U` does not already contain). The caller feeds the
276
+ * vocabulary; the element's own droppable tokens are part of it, guaranteeing feasibility and the
277
+ * "never worse than the original" property.
278
+ * 3. COST(class) = token length + 1 (the token plus its joining space) — so minimizing total cost
279
+ * minimizes the rendered `class="…"` byte length exactly.
280
+ * 4. MIN-COST EXACT COVER via bitmask DP over `U`'s tuples: `dp[coveredMask]` = least cost reaching
281
+ * that coverage; each transition adds one candidate's mask. Because every candidate AGREES with
282
+ * `U` on every tuple it sets, ANY full cover reproduces `U` at the tuple level.
283
+ * 5. `|U|` is BOUNDED ({@link DEFAULT_MAX_UNIVERSE}); a larger condition-block returns `null` so the
284
+ * caller falls back to its greedy emit for that element.
264
285
  *
265
- * (Chosen as the safe variant of the size-hoisting "full-size passthrough" idea: hoisting an explicit
266
- * `width/height:100%` onto a child is only sound when the child is block-level and unsized, which is
267
- * not knowable from the wrapper alone whereas a `display:contents` box is a passthrough by definition.)
286
+ * The **correctness backstop** (re-resolve the chosen set and assert it equals `U` exactly) lives in
287
+ * each resolver's `emit` it owns the forward `resolve`, and running the check there keeps this core
288
+ * helper pure and provider-agnostic. A chosen set that fails the backstop is discarded and the greedy
289
+ * emit is used instead, so a set that does not reproduce `U` is NEVER emitted.
268
290
  */
291
+
269
292
  /**
270
- * Flatten a `display:contents` wrapper (a box that generates no box) into its sole element child,
271
- * folding any inheritable styles down first so inherited values survive the removal.
293
+ * The canonical key for one normalized declaration under one style condition: the atomic unit both the
294
+ * target universe and every vocabulary class are expressed in. Equal keys ⇔ the SAME declaration.
272
295
  */
273
- declare const displayContentsWrapper: AuthoredPattern<Captures>;
274
-
296
+ declare function tupleKey(condition: string, property: string, value: string, important: boolean): string;
275
297
  /**
276
- * @domflax/patterns flatten pattern: `empty-style-div`.
277
- *
278
- * Collapses the most common piece of structural noise of all: a `<div>` whose ONLY role is to wrap
279
- * a single child while contributing nothing to layout or paint —
280
- *
281
- * <div><Child/></div> (no styles at all)
282
- * <div style="display:block"><Child/></div> (the default; still a no-op box)
298
+ * Flatten a StyleMap into its set of {@link tupleKey}s (normalizing first). This is how BOTH the target
299
+ * universe and each vocabulary class's declarations are lowered, so a class's tuples can be tested for
300
+ * subset-membership in the universe by plain string equality.
301
+ */
302
+ declare function styleMapTuples(map: StyleMap, norm: StyleNormalizer): string[];
303
+ /** One vocabulary entry: a class token and the {@link tupleKey}s its full declaration set produces. */
304
+ interface CoverClass {
305
+ readonly token: string;
306
+ readonly tuples: readonly string[];
307
+ }
308
+ interface MinCoverOptions {
309
+ /** Upper bound on `|U|`; above it the DP is skipped (`null`) so the caller uses its greedy emit. */
310
+ readonly maxUniverse?: number;
311
+ }
312
+ /**
313
+ * The largest universe the bitmask DP will solve. 20 tuples ⇒ a `2^20` (~1M) DP table, comfortably
314
+ * fast and bounded in memory; a heavier element (rare) falls back to the resolver's greedy emit.
315
+ */
316
+ declare const DEFAULT_MAX_UNIVERSE = 20;
317
+ /**
318
+ * Solve the minimal-string exact cover of `universe` using `vocabulary`.
283
319
  *
284
- * Such a div is layout-neutral: it is a plain block box with no own visual style, establishes no
285
- * box / formatting / stacking context, is not a containing block, and declares no custom properties
286
- * a descendant might read. Its box is therefore indistinguishable from "not being there", so it can
287
- * be unwrapped into its sole child.
320
+ * Returns the chosen class tokens (sorted, de-duplicated) whose union reproduces `universe` EXACTLY at
321
+ * least total string cost, or `null` when there is no exact cover OR the universe exceeds the bound (in
322
+ * both cases the caller falls back to its greedy emit). An empty universe yields `[]`.
288
323
  *
289
- * Authored with the declarative {@link definePattern} API. The opacity-barrier + selector-safety
290
- * guards (ref/handlers/dynamic-children/raw-html/combinator/reparent-impact) are applied
291
- * automatically for every `flatten/*` pattern; the `where` predicates below add the LAYOUT-neutrality
292
- * requirements specific to this pattern (no non-block display, no box/formatting/stacking context, no
293
- * containing block, no custom-property coupling, no structural-pseudo targeting).
324
+ * Pure and provider-agnostic: the caller performs the re-resolve correctness backstop.
294
325
  */
326
+ declare function minStringCover(universe: readonly string[], vocabulary: Iterable<CoverClass>, options?: MinCoverOptions): readonly string[] | null;
327
+
295
328
  /**
296
- * Flatten a layout-neutral, style-free `<div>` wrapper into its sole element child.
329
+ * domflax build-end optimization SUMMARY.
330
+ *
331
+ * A tiny, dependency-free formatter shared by the Vite and webpack/Next adapters. Each adapter
332
+ * accumulates {@link FileStatDelta} numbers across the build into a {@link Totals}, then prints ONE
333
+ * boxed {@link renderSummary} block at build end — so the user sees the aggregate payoff without any
334
+ * per-file spam in between.
335
+ *
336
+ * ```
337
+ * ▲ domflax
338
+ * ────────────────────────────────
339
+ * files optimized 42
340
+ * DOM nodes removed 318
341
+ * classes compressed 1,204
342
+ * size saved 18.7 KB
343
+ * ────────────────────────────────
344
+ * ```
297
345
  */
298
- declare const emptyStyleDiv: AuthoredPattern<Captures>;
346
+ /** Per-file optimization delta (from a single {@link Domflax.transform}). */
347
+ interface FileStatDelta {
348
+ /** DOM/IR nodes removed by provably-safe flattens. */
349
+ readonly nodesRemoved: number;
350
+ /** Class tokens eliminated by semantic compression. */
351
+ readonly classesSaved: number;
352
+ /** Bytes saved = original byte length − output byte length (may be negative in edge cases). */
353
+ readonly bytesSaved: number;
354
+ }
299
355
 
300
356
  /**
301
357
  * @domflax/patterns — flatten pattern: `flex-center-wrapper`.
@@ -320,108 +376,6 @@ declare const emptyStyleDiv: AuthoredPattern<Captures>;
320
376
  */
321
377
  declare const flexCenterWrapper: AuthoredPattern<Captures>;
322
378
 
323
- /**
324
- * @domflax/patterns — flatten pattern: `inline-flex-center-wrapper`.
325
- *
326
- * Collapses the inline-flex flavour of the "centering wrapper" idiom
327
- *
328
- * <div style="display:inline-flex; align-items:center; justify-content:center"><Child/></div>
329
- *
330
- * into its sole child, pushing the centering intent down onto the child as `place-self: center`.
331
- * Like its block-level `flex-center-wrapper` sibling the wrapper only exists to center one element;
332
- * once `place-self:center` lives on the child the wrapper is pure structural noise and can go.
333
- *
334
- * Authored with the declarative {@link definePattern} API: the match is the inline-flex-centering
335
- * computed-style signature on a single-element-child `<div>` that paints nothing of its own; the
336
- * recipe folds inheritable styles onto the child, grants it `place-self:center`, then unwraps the
337
- * wrapper (id-preserving). The opacity-barrier + selector-safety guards are applied automatically by
338
- * the `definePattern` factory for every `flatten/*` pattern.
339
- */
340
- /**
341
- * Flatten an inline-flex-centering `<div>` wrapper into its sole element child, granting the child
342
- * `place-self:center`.
343
- */
344
- declare const inlineFlexCenterWrapper: AuthoredPattern<Captures>;
345
-
346
- /**
347
- * @domflax/patterns — flatten pattern: `nested-flex-merge`.
348
- *
349
- * Collapses a redundant nesting of two flex containers
350
- *
351
- * <div style="display:flex; align-items:center; gap:8px">
352
- * <div style="display:flex; flex-direction:column"> … </div>
353
- * </div>
354
- *
355
- * where the OUTER flex container's sole element child is ITSELF a flex container, into a single
356
- * flex container that carries the union of both elements' flex declarations. The outer wrapper's
357
- * box is then structural noise (it paints nothing and only establishes a flex context that the
358
- * merged child now also establishes), so it is removed.
359
- *
360
- * Authored with the declarative {@link pattern} API: the match is a flex `<div>` with a single
361
- * element child painting nothing of its own (auto-guarded against opacity barriers / combinator
362
- * targeting like every `flatten/*` pattern). The value-relational reasoning — the child must also
363
- * be a (non-combinator) flex container, the wrapper must carry only transferable flex/inheritable
364
- * declarations, and the two must not conflict on any shared flex property — lives in the `rewrite`
365
- * op-draft factory escape hatch, which folds inherited styles, transfers the wrapper's flex
366
- * declarations onto the child (target-wins), then unwraps the wrapper.
367
- */
368
- /**
369
- * Flatten a flex container whose sole child is a compatible flex container into a single container.
370
- */
371
- declare const nestedFlexMerge: AuthoredPattern<Captures>;
372
-
373
- /**
374
- * @domflax/patterns — flatten pattern: `nested-grid-merge`.
375
- *
376
- * Collapses a redundant nesting of two grid containers
377
- *
378
- * <div style="display:grid; gap:8px">
379
- * <div style="display:grid; grid-template-columns:1fr 1fr"> … </div>
380
- * </div>
381
- *
382
- * where the OUTER grid container's sole element child is ITSELF a grid container, into a single grid
383
- * container carrying the union of both elements' grid declarations. The outer wrapper's box is then
384
- * structural noise (it paints nothing and only establishes a grid context the merged child now also
385
- * establishes), so it is removed.
386
- *
387
- * This is the grid analogue of `nested-flex-merge`. The declarative match is a grid `<div>` with a
388
- * single element child painting nothing of its own (auto-guarded against opacity barriers / combinator
389
- * targeting like every `flatten/*` pattern). The value-relational reasoning — the child must also be a
390
- * (non-combinator) grid container, the wrapper must carry only transferable grid/inheritable
391
- * declarations, and the two must not conflict on any shared grid property — lives in the `rewrite`
392
- * op-draft factory escape hatch, which folds inherited styles, transfers the wrapper's grid
393
- * declarations onto the child (target-wins), then unwraps the wrapper.
394
- */
395
- /**
396
- * Flatten a grid container whose sole child is a compatible grid container into a single container.
397
- */
398
- declare const nestedGridMerge: AuthoredPattern<Captures>;
399
-
400
- /**
401
- * @domflax/patterns — flatten pattern: `passthrough-wrapper`.
402
- *
403
- * Collapses a purely-structural wrapper that exists for no reason at all:
404
- *
405
- * <div><Child/></div>
406
- *
407
- * The wrapper paints nothing, establishes no box / formatting / stacking context, carries no
408
- * attributes beyond an (optional) inert class, holds exactly one element child, and is free of every
409
- * opacity barrier (ref / event-handlers / dynamic children / dangerous html / spread / component).
410
- * Such a `<div>` is pure DOM noise: removing it and hoisting the child is invisible to both paint
411
- * and layout.
412
- *
413
- * Authored with the declarative {@link definePattern} API. The opacity-barrier + selector-safety
414
- * guards (ref/handlers/dynamic-children/raw-html/combinator/reparent-impact) are applied
415
- * automatically for every `flatten/*` pattern; the `where` predicates add the passthrough-specific
416
- * requirements (no box/formatting/stacking context, no own attrs, no dynamic/spread classes, not a
417
- * component, not a structural-pseudo subject).
418
- */
419
- /**
420
- * Flatten a do-nothing `<div>` wrapper into its sole element child, folding any inheritable styles
421
- * down first so inherited values survive the box removal.
422
- */
423
- declare const passthroughWrapper: AuthoredPattern<Captures>;
424
-
425
379
  /**
426
380
  * @domflax/patterns — flatten pattern: `redundant-fragment`.
427
381
  *
@@ -447,378 +401,167 @@ declare const passthroughWrapper: AuthoredPattern<Captures>;
447
401
  declare const redundantFragment: AuthoredPattern<Captures>;
448
402
 
449
403
  /**
450
- * @domflax/patterns — flatten pattern: `redundant-inline-wrapper`.
404
+ * @domflax/patterns — flatten pattern: `grid-center-wrapper`.
451
405
  *
452
- * Collapses a purely-structural INLINE wrapper:
406
+ * Collapses the grid flavour of the "centering wrapper" idiom
453
407
  *
454
- * <span><Child/></span> (display:inline, no own style)
408
+ * <div style="display:grid; align-items:center; justify-content:center"><Child/></div>
455
409
  *
456
- * An inline `<span>` that paints nothing, establishes no box / formatting / stacking context, carries
457
- * no attributes beyond an (optional) inert class, declares no custom properties, and holds exactly one
458
- * element child is pure inline noise. An empty inline box merely wraps its child's box; removing it and
459
- * hoisting the child leaves both paint and layout untouched (the surviving child folds the inheritable
460
- * declarations the span carried).
410
+ * into its sole child, pushing the centering intent down onto the child as `place-self:center`. This is
411
+ * the grid analogue of `flex-center-wrapper`: the wrapper only exists to center one element, and once
412
+ * `place-self:center` lives on the child the wrapper is pure structural noise.
461
413
  *
462
- * This is the inline sibling of `passthrough-wrapper` (which targets `<div>`): the same opacity-barrier
463
- * + selector-safety guards are auto-applied by the `pattern()` factory for every `flatten/*` pattern;
464
- * the `where` predicates add the inline-passthrough requirements (display must be the inline default,
465
- * no box/formatting/stacking context or var coupling, no own attrs / dynamic-or-spread classes, not a
466
- * component, not a structural-pseudo subject).
467
- */
468
- /**
469
- * Flatten a do-nothing inline `<span>` wrapper into its sole element child, folding any inheritable
470
- * styles down first so inherited values survive the box removal.
471
- */
472
- declare const redundantInlineWrapper: AuthoredPattern<Captures>;
473
-
474
- /**
475
- * @domflax/patterns — compress pattern: `border-radius-shorthand`.
476
- *
477
- * Collapses an element whose four corner radii are expressed as separate longhand declarations and
478
- * are ALL EQUAL into the single CSS `border-radius` shorthand:
479
- *
480
- * border-top-left-radius:0.5rem; border-top-right-radius:0.5rem;
481
- * border-bottom-right-radius:0.5rem; border-bottom-left-radius:0.5rem
482
- * ⇒ border-radius:0.5rem (Tailwind `rounded-lg`)
483
- *
484
- * The IR's computed StyleMap keeps each corner as its own longhand (Tailwind's `rounded-tl-*` /
485
- * `rounded-tr-*` / … each resolve to one corner property). This pass runs the collapse in reverse on
486
- * the computed map ONLY when all four corners share one value — the single case that maps cleanly to
487
- * a single Tailwind utility (the CSS 2-value `border-radius` form is DIAGONAL, which has no clean
488
- * `rounded-*` edge utility, so per-corner differences are intentionally left alone). Rebuilding the
489
- * map with one `border-radius` decl lets the minimizing reverse-emit pick the single `rounded-*`
490
- * token covering all four corners instead of two edge tokens.
491
- *
492
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
493
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
494
- * HTML never blocks a class-only rewrite); the
495
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the four corners are
496
- * present, concrete, equal, and share an `!important` flag.
497
- */
498
- /** Fold four equal corner radii into the single `border-radius` shorthand. */
499
- declare const borderRadiusShorthand: AuthoredPattern<Captures>;
500
-
501
- /**
502
- * @domflax/patterns — compress pattern: `border-shorthand`.
503
- *
504
- * Collapses an element whose four border-side WIDTHS are expressed as separate longhand declarations
505
- * back into the shortest equivalent `border-width` shorthand:
506
- *
507
- * border-top-width:2px; border-right-width:2px; border-bottom-width:2px; border-left-width:2px
508
- * ⇒ border-width:2px (Tailwind `border-2`)
509
- *
510
- * border-top-width:2px; border-bottom-width:2px; border-left-width:4px; border-right-width:4px
511
- * ⇒ border-width:2px 4px (Tailwind `border-y-2 border-x-4`)
512
- *
513
- * Tailwind's per-side / per-axis width utilities (`border-t-*`, `border-x-*`, …) each resolve to the
514
- * matching `border-*-width` longhand(s); the shared normalizer keeps them longhand. This pass runs
515
- * the expansion in reverse on the computed map ONLY when the four widths fold cleanly into a 1- or
516
- * 2-value form — i.e. `top===bottom` AND `left===right`. Rebuilding the map with one `border-width`
517
- * shorthand lets the minimizing reverse-emit pick the single/paired utility (`border-2`, or
518
- * `border-x-* border-y-*`) instead of four per-side tokens. Only WIDTH is folded — border style and
519
- * color are independent longhands the resolver carries separately, so this never disturbs them.
520
- *
521
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
522
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
523
- * HTML never blocks a class-only rewrite); the `rewriteClasses`
524
- * recipe rebuilds the class StyleMap, declining (`null`) unless the four widths fold cleanly.
525
- */
526
- /** Compress an element's four equal/paired border-width longhands into the shortest shorthand. */
527
- declare const borderShorthand: AuthoredPattern<Captures>;
528
-
529
- /**
530
- * @domflax/patterns — compress pattern: `dedupe-classes`.
531
- *
532
- * Removes duplicate / fully-overridden class tokens that resolve to the same property where a
533
- * LATER token wins, leaving the minimal set of tokens with an IDENTICAL computed style. The
534
- * canonical case:
535
- *
536
- * <p class="text-sm text-lg">…</p> → <p class="text-lg">…</p>
537
- *
538
- * Both `text-sm` and `text-lg` set `font-size`; resolution already made `text-lg` win, so the
539
- * computed `font-size` is `text-lg`'s value and `text-sm` contributes NOTHING to the final
540
- * computed style. The earlier token is pure noise and can be dropped without changing a pixel.
541
- *
542
- * How redundancy is detected (purely from the already-resolved, normalized computed StyleMap):
543
- * • every declaration carries provenance — `origin` (the winning token) and `shadowed`
544
- * (the tokens it overrode);
545
- * • a class token is FULLY OVERRIDDEN iff it appears in some declaration's `shadowed` list but
546
- * is NOT the winning `origin` of any declaration across ANY style condition. Such a token can
547
- * be deleted with zero effect on the computed style.
548
- *
549
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
550
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
551
- * HTML never blocks a class-only rewrite); the `dropClasses` recipe returns the set of
552
- * fully-overridden, resolver-droppable tokens to delete (their `shadowed` provenance is pruned
553
- * automatically before the minimal class StyleMap is re-installed).
554
- */
555
- /**
556
- * Collapse a class list to the minimal token set that yields an identical computed style, by
557
- * dropping tokens whose declarations are fully overridden by later tokens.
558
- */
559
- declare const dedupeClasses: AuthoredPattern<Captures>;
560
-
561
- /**
562
- * @domflax/patterns — compress pattern: `gap-shorthand`.
563
- *
564
- * Collapses an element whose grid/flex gutters are expressed as two equal axis longhands back into
565
- * the single `gap` shorthand:
566
- *
567
- * row-gap:16px; column-gap:16px ⇒ gap:16px (Tailwind `gap-x-4 gap-y-4` → `gap-4`)
568
- *
569
- * The IR's computed StyleMap is canonically LONGHAND (the shared normalizer expands the `gap`
570
- * shorthand into `row-gap` + `column-gap` at parse time). This pass runs the expansion in reverse on
571
- * the computed map ONLY when both axes carry the SAME value and `!important` flag — i.e. when the two
572
- * gutters genuinely fold into a single-value `gap`. When the axes differ it declines, leaving the two
573
- * longhands verbatim (an asymmetric gutter has no equivalent single-value shorthand).
574
- *
575
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
576
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
577
- * HTML never blocks a class-only rewrite); the
578
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the two axis gaps
579
- * are present, equal, and share an `!important` flag.
580
- */
581
- /** Fold an equal `row-gap`/`column-gap` pair into the single `gap` shorthand. */
582
- declare const gapShorthand: AuthoredPattern<Captures>;
583
-
584
- /**
585
- * @domflax/patterns — compress pattern: `inset-shorthand`.
414
+ * Authored with the declarative {@link definePattern} API: the match is the grid-centering computed-style
415
+ * signature on a single-element-child `<div>` that paints nothing of its own; the recipe folds
416
+ * inheritable styles onto the child, grants it `place-self:center`, then unwraps the wrapper. The
417
+ * opacity-barrier + selector-safety guards are auto-applied for every `flatten/*` pattern.
586
418
  *
587
- * Recompacts the four physical inset longhands (`top`/`right`/`bottom`/`left`) on an element's
588
- * computed style back into the tightest CSS shorthand the values allow:
589
- *
590
- * all four equal → `inset: <v>`
591
- * • top == bottom (a matching pair) → `inset-block: <v>` (Tailwind `inset-y-*`)
592
- * • left == right (a matching pair) → `inset-inline: <v>` (Tailwind `inset-x-*`)
593
- *
594
- * The two axis collapses are independent: an element whose `top == bottom` but `left != right`
595
- * collapses only the block axis and keeps the `left`/`right` longhands verbatim. When nothing
596
- * collapses (all four distinct, or fewer than a full pair present) the pattern declines.
597
- *
598
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
599
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
600
- * HTML never blocks a class-only rewrite); the `rewriteClasses` recipe rebuilds the class
601
- * StyleMap, declining (`null`) unless at least one inset axis collapses.
419
+ * Genuinely additive under the conservative gate: unlike a plain passthrough (which the gate REVERTS on
420
+ * a grid wrapper because grid establishes a formatting context), the compensating `place-self:center`
421
+ * makes the flatten `provably-safe` — but ONLY when the child's NEW parent is a statically-known grid
422
+ * that lets the wrapper fill its area (the ONE context where the child's `justify-self` is honored).
602
423
  */
603
424
  /**
604
- * Collapse equal/paired physical inset longhands into the `inset` / `inset-block` / `inset-inline`
605
- * shorthands on an element's computed style.
425
+ * Flatten a grid-centering `<div>` wrapper into its sole element child, granting the child
426
+ * `place-self:center`.
606
427
  */
607
- declare const insetShorthand: AuthoredPattern<Captures>;
428
+ declare const gridCenterWrapper: AuthoredPattern<Captures>;
608
429
 
609
430
  /**
610
- * @domflax/patterns — compress pattern: `margin-shorthand`.
611
- *
612
- * Collapses the four explicit margin longhands
431
+ * @domflax/patterns — flatten pattern: `display-contents-wrapper`.
613
432
  *
614
- * margin-top / margin-right / margin-bottom / margin-left
433
+ * Collapses a wrapper that has explicitly opted OUT of generating a box:
615
434
  *
616
- * back into a single CSS `margin` shorthand declaration on the SAME element (the margin analogue of
617
- * `padding-shorthand`, covering the `m` / `mx` / `my` collapse), choosing the shortest legal
618
- * 1–4-value form:
435
+ * <div style="display:contents"><Child/></div> → <Child/>
619
436
  *
620
- * all four equal → `margin: <v>` (the `m` case)
621
- * top==bottom and left==right → `margin: <y> <x>` (the `my`/`mx` case)
622
- * left==right (top!=bottom) → `margin: <t> <x> <b>`
623
- * otherwise → `margin: <t> <r> <b> <l>`
437
+ * `display:contents` makes an element generate NO box of its own — its children render exactly as if
438
+ * they were direct children of the element's parent. A `display:contents` wrapper with a single
439
+ * element child is therefore already a layout passthrough: it contributes nothing to flow, paint
440
+ * (a contents box paints nothing), formatting, stacking, or containing-block resolution. Removing it
441
+ * and hoisting the child produces a tree that is layout-identical — the wrapper's only remaining
442
+ * effect was inheritance, which is preserved by folding inheritable declarations onto the child first.
624
443
  *
625
- * It is a pure representation change: the resolved box model is identical, only the declaration
626
- * count shrinks from four to one, which the backend can then re-emit as a single shorthand utility.
444
+ * This is the safest possible wrapper-elimination: the box being removed provably did not exist.
445
+ * The opacity-barrier + selector-safety guards (ref/handlers/dynamic-children/raw-html/combinator/
446
+ * reparent-impact) are auto-applied for every `flatten/*` pattern; the `where` predicates add the
447
+ * passthrough-specific requirements (no own attrs / dynamic-or-spread classes, no `var()` coupling,
448
+ * not a component, not a structural-pseudo subject).
627
449
  *
628
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
629
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
630
- * HTML never blocks a class-only rewrite); the `rewriteClasses` recipe rebuilds the class
631
- * StyleMap, declining (`null`) unless all four margin longhands are present with a uniform
632
- * (non-)`!important` flag.
450
+ * (Chosen as the safe variant of the size-hoisting "full-size passthrough" idea: hoisting an explicit
451
+ * `width/height:100%` onto a child is only sound when the child is block-level and unsized, which is
452
+ * not knowable from the wrapper alone whereas a `display:contents` box is a passthrough by definition.)
633
453
  */
634
454
  /**
635
- * Fold four margin longhands into one `margin` shorthand on the element's base style block.
455
+ * Flatten a `display:contents` wrapper (a box that generates no box) into its sole element child,
456
+ * folding any inheritable styles down first so inherited values survive the removal.
636
457
  */
637
- declare const marginShorthand: AuthoredPattern<Captures>;
458
+ declare const displayContentsWrapper: AuthoredPattern<Captures>;
638
459
 
639
460
  /**
640
- * @domflax/patterns — compress pattern: `overflow-shorthand`.
461
+ * @domflax/patterns — flatten pattern: `empty-style-div`.
641
462
  *
642
- * Collapses an element whose two overflow axes are expressed as equal longhands back into the single
643
- * `overflow` shorthand:
463
+ * Collapses the most common piece of structural noise of all: a `<div>` whose ONLY role is to wrap
464
+ * a single child while contributing nothing to layout or paint —
644
465
  *
645
- * overflow-x:auto; overflow-y:auto ⇒ overflow:auto (Tailwind `overflow-x-auto overflow-y-auto`
646
- * `overflow-auto`)
466
+ * <div><Child/></div> (no styles at all)
467
+ * <div style="display:block"><Child/></div> (the default; still a no-op box)
647
468
  *
648
- * Unlike the box shorthands, the shared normalizer leaves `overflow-x` / `overflow-y` as independent
649
- * longhands (it does not synthesize an `overflow` shorthand), so an element styled with two equal
650
- * axis utilities keeps two separate declarations until this pass folds them. The fold runs ONLY when
651
- * both axes carry the SAME value and `!important` flag; an asymmetric pair (`overflow-x !==
652
- * overflow-y`) has no single-keyword `overflow` equivalent and is declined.
469
+ * Such a div is layout-neutral: it is a plain block box with no own visual style, establishes no
470
+ * box / formatting / stacking context, is not a containing block, and declares no custom properties
471
+ * a descendant might read. Its box is therefore indistinguishable from "not being there", so it can
472
+ * be unwrapped into its sole child.
653
473
  *
654
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
655
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
656
- * HTML never blocks a class-only rewrite); the
657
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless both overflow axes
658
- * are present, equal, and share an `!important` flag.
474
+ * Authored with the declarative {@link definePattern} API. The opacity-barrier + selector-safety
475
+ * guards (ref/handlers/dynamic-children/raw-html/combinator/reparent-impact) are applied
476
+ * automatically for every `flatten/*` pattern; the `where` predicates below add the LAYOUT-neutrality
477
+ * requirements specific to this pattern (no non-block display, no box/formatting/stacking context, no
478
+ * containing block, no custom-property coupling, no structural-pseudo targeting).
659
479
  */
660
- /** Fold an equal `overflow-x`/`overflow-y` pair into the single `overflow` shorthand. */
661
- declare const overflowShorthand: AuthoredPattern<Captures>;
662
-
663
480
  /**
664
- * @domflax/patterns compress pattern: `overscroll-behavior-shorthand`.
665
- *
666
- * Collapses an element whose two overscroll-behavior axes are expressed as separate longhand
667
- * declarations and are EQUAL into the single CSS `overscroll-behavior` shorthand:
668
- *
669
- * overscroll-behavior-x:contain; overscroll-behavior-y:contain
670
- * ⇒ overscroll-behavior:contain (Tailwind `overscroll-x-contain overscroll-y-contain` → `overscroll-contain`)
671
- *
672
- * Tailwind's `overscroll-x-*` / `overscroll-y-*` utilities each resolve to the matching
673
- * `overscroll-behavior-{x,y}` axis longhand, and the shared normalizer keeps `overscroll-behavior`
674
- * un-expanded (it is NOT one of the box/gap shorthands the normalizer splits). So only the equal-axis
675
- * form maps cleanly to a single `overscroll-*` utility. This pass runs the collapse in reverse on the
676
- * computed map ONLY when both axes carry the SAME value and `!important` flag, replacing them with one
677
- * `overscroll-behavior` decl so the minimizing reverse-emit can pick a single `overscroll-*` token
678
- * instead of two axis tokens. When the axes differ it declines, leaving the two longhands verbatim.
679
- *
680
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
681
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
682
- * HTML never blocks a class-only rewrite); the
683
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless both axes are
684
- * present, concrete, equal, and share an `!important` flag.
481
+ * Flatten a layout-neutral, style-free `<div>` wrapper into its sole element child.
685
482
  */
686
- /** Fold an equal overscroll-behavior x/y pair into the single `overscroll-behavior` shorthand. */
687
- declare const overscrollBehaviorShorthand: AuthoredPattern<Captures>;
483
+ declare const emptyStyleDiv: AuthoredPattern<Captures>;
688
484
 
689
485
  /**
690
- * @domflax/patterns — compress pattern: `padding-shorthand`.
486
+ * @domflax/patterns — flatten pattern: `inherited-only-wrapper`.
691
487
  *
692
- * Collapses an element whose four padding sides are expressed as separate longhand declarations
693
- * back into the shortest equivalent shorthand:
488
+ * Collapses a paint-free wrapper whose ONLY own declarations are INHERITED properties
489
+ * (`text-align`, `color`, `font-*`, `line-height`, `letter-spacing`, `white-space`, …):
694
490
  *
695
- * padding-top:16px; padding-right:16px; padding-bottom:16px; padding-left:16px
696
- * ⇒ padding:16px (Tailwind `p-4`)
491
+ * <div style="text-align:center"><Child/></div> → <Child style="text-align:center"/>
697
492
  *
698
- * padding-top:8px; padding-bottom:8px; padding-left:16px; padding-right:16px
699
- * ⇒ padding:8px 16px (Tailwind `px-4 py-2`)
493
+ * An inherited property on a wrapper reaches its descendants purely through inheritance, so folding it
494
+ * onto the sole child and removing the box is render-identical — the child (and everything below it)
495
+ * still sees the same inherited value. The wrapper carries nothing NON-inherited (no padding, margin,
496
+ * sizing, border, background, layout), so its box contributes nothing to flow or paint.
700
497
  *
701
- * The IR's computed StyleMap is canonically LONGHAND (the shared normalizer expands every box
702
- * shorthand at parse time). This pass runs the expansion in reverse on the computed map ONLY when
703
- * the four sides fold cleanly into a 1- or 2-value form i.e. `top===bottom` AND `left===right`.
704
- *
705
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
706
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
707
- * HTML never blocks a class-only rewrite); the `rewriteClasses`
708
- * recipe rebuilds the class StyleMap, declining (`null`) unless the four sides fold cleanly.
498
+ * Distinct from `passthrough-wrapper` in INTENT: it recognizes the common "styling wrapper" idiom
499
+ * a box that exists only to set an inherited text/font property on a subtree and pushes that intent
500
+ * down onto the child. `foldInheritedStyles` (auto-applied by the flatten recipe) performs the fold;
501
+ * the `where` predicate restricts the match to wrappers whose entire own style is inheritable.
709
502
  */
710
503
  /**
711
- * Compress an element's four equal/paired padding longhands into the shortest `padding` shorthand.
504
+ * Flatten a wrapper whose only own style is inherited into its sole element child (folding the
505
+ * inherited declarations down first so the subtree keeps the same inherited values).
712
506
  */
713
- declare const paddingShorthand: AuthoredPattern<Captures>;
507
+ declare const inheritedOnlyWrapper: AuthoredPattern<Captures>;
714
508
 
715
509
  /**
716
- * @domflax/patterns — compress pattern: `place-shorthand`.
510
+ * @domflax/patterns — flatten pattern: `passthrough-wrapper`.
717
511
  *
718
- * Recompacts the grid/flex alignment longhands on an element's computed style into the CSS `place-*`
719
- * shorthands whenever the two axes of a pair agree:
512
+ * Collapses a purely-structural wrapper that exists for no reason at all:
720
513
  *
721
- * • align-items == justify-items → `place-items: <v>` (Tailwind `items-* justify-items-*`)
722
- * • align-content == justify-content → `place-content: <v>` (Tailwind `content-* justify-*`)
514
+ * <div><Child/></div>
723
515
  *
724
- * The two collapses are INDEPENDENT: an element whose items pair agrees but whose content pair does
725
- * not collapses only `place-items` and keeps the content longhands verbatim. The shared normalizer
726
- * leaves all four alignment properties as independent longhands (it synthesizes no `place-*`
727
- * shorthand), so an element styled with two matching axis utilities keeps the longhands until this
728
- * pass folds them. When neither pair agrees the pattern declines.
516
+ * The wrapper paints nothing, establishes no box / formatting / stacking context, carries no
517
+ * attributes beyond an (optional) inert class, holds exactly one element child, and is free of every
518
+ * opacity barrier (ref / event-handlers / dynamic children / dangerous html / spread / component).
519
+ * Such a `<div>` is pure DOM noise: removing it and hoisting the child is invisible to both paint
520
+ * and layout.
729
521
  *
730
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
731
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
732
- * HTML never blocks a class-only rewrite); the
733
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless at least one
734
- * alignment pair collapses.
522
+ * Authored with the declarative {@link definePattern} API. The opacity-barrier + selector-safety
523
+ * guards (ref/handlers/dynamic-children/raw-html/combinator/reparent-impact) are applied
524
+ * automatically for every `flatten/*` pattern; the `where` predicates add the passthrough-specific
525
+ * requirements (no box/formatting/stacking context, no own attrs, no dynamic/spread classes, not a
526
+ * component, not a structural-pseudo subject).
735
527
  */
736
- /** Fold matching align/justify pairs into the `place-items` / `place-content` shorthands. */
737
- declare const placeShorthand: AuthoredPattern<Captures>;
738
-
739
528
  /**
740
- * @domflax/patterns compress pattern: `scroll-margin-shorthand`.
741
- *
742
- * Collapses an element whose four scroll-margin sides are expressed as separate longhand
743
- * declarations and are ALL EQUAL into the single CSS `scroll-margin` shorthand:
744
- *
745
- * scroll-margin-top:1rem; scroll-margin-right:1rem;
746
- * scroll-margin-bottom:1rem; scroll-margin-left:1rem
747
- * ⇒ scroll-margin:1rem (Tailwind `scroll-m-4`)
748
- *
749
- * Tailwind's `scroll-mt-*` / `scroll-mx-*` / … utilities each resolve to the matching
750
- * `scroll-margin-*` longhand(s), and the shared normalizer keeps `scroll-margin` un-expanded (it is
751
- * NOT one of the box shorthands the normalizer splits). So only the all-equal (1-value) form maps
752
- * cleanly to a single `scroll-m-*` utility — the 2-value (`scroll-mx`/`scroll-my`) shape is left to
753
- * the resolver's own reverse-emit. This pass runs the collapse in reverse on the computed map ONLY
754
- * when all four sides share one value, replacing them with one `scroll-margin` decl so the minimizing
755
- * reverse-emit can pick a single `scroll-m-*` token instead of two axis tokens.
756
- *
757
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
758
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
759
- * HTML never blocks a class-only rewrite); the
760
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the four sides are
761
- * present, concrete, equal, and share an `!important` flag.
529
+ * Flatten a do-nothing `<div>` wrapper into its sole element child, folding any inheritable styles
530
+ * down first so inherited values survive the box removal.
762
531
  */
763
- /** Fold four equal scroll-margin sides into the single `scroll-margin` shorthand. */
764
- declare const scrollMarginShorthand: AuthoredPattern<Captures>;
532
+ declare const passthroughWrapper: AuthoredPattern<Captures>;
765
533
 
766
534
  /**
767
- * @domflax/patterns — compress pattern: `scroll-padding-shorthand`.
535
+ * @domflax/patterns — flatten pattern: `redundant-inline-wrapper`.
768
536
  *
769
- * Collapses an element whose four scroll-padding sides are expressed as separate longhand
770
- * declarations and are ALL EQUAL into the single CSS `scroll-padding` shorthand:
537
+ * Collapses a purely-structural INLINE wrapper:
771
538
  *
772
- * scroll-padding-top:1rem; scroll-padding-right:1rem;
773
- * scroll-padding-bottom:1rem; scroll-padding-left:1rem
774
- * ⇒ scroll-padding:1rem (Tailwind `scroll-p-4`)
539
+ * <span><Child/></span> (display:inline, no own style)
775
540
  *
776
- * Tailwind's `scroll-pt-*` / `scroll-px-*` / utilities each resolve to the matching
777
- * `scroll-padding-*` longhand(s), and the shared normalizer keeps `scroll-padding` un-expanded (it is
778
- * NOT one of the box shorthands the normalizer splits). So only the all-equal (1-value) form maps
779
- * cleanly to a single `scroll-p-*` utility the 2-value (`scroll-px`/`scroll-py`) shape is left to
780
- * the resolver's own reverse-emit. This pass runs the collapse in reverse on the computed map ONLY
781
- * when all four sides share one value, replacing them with one `scroll-padding` decl so the minimizing
782
- * reverse-emit can pick a single `scroll-p-*` token instead of two axis tokens.
541
+ * An inline `<span>` that paints nothing, establishes no box / formatting / stacking context, carries
542
+ * no attributes beyond an (optional) inert class, declares no custom properties, and holds exactly one
543
+ * element child is pure inline noise. An empty inline box merely wraps its child's box; removing it and
544
+ * hoisting the child leaves both paint and layout untouched (the surviving child folds the inheritable
545
+ * declarations the span carried).
783
546
  *
784
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
785
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
786
- * HTML never blocks a class-only rewrite); the
787
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the four sides are
788
- * present, concrete, equal, and share an `!important` flag.
547
+ * This is the inline sibling of `passthrough-wrapper` (which targets `<div>`): the same opacity-barrier
548
+ * + selector-safety guards are auto-applied by the `pattern()` factory for every `flatten/*` pattern;
549
+ * the `where` predicates add the inline-passthrough requirements (display must be the inline default,
550
+ * no box/formatting/stacking context or var coupling, no own attrs / dynamic-or-spread classes, not a
551
+ * component, not a structural-pseudo subject).
789
552
  */
790
- /** Fold four equal scroll-padding sides into the single `scroll-padding` shorthand. */
791
- declare const scrollPaddingShorthand: AuthoredPattern<Captures>;
792
-
793
553
  /**
794
- * @domflax/patterns compress pattern: `size-shorthand`.
795
- *
796
- * Collapses an element whose computed `width` and `height` are EQUAL into the single Tailwind
797
- * `size-*` utility:
798
- *
799
- * <div style="width:1rem; height:1rem"/> → <div class="size-4"/>
800
- *
801
- * At the IR level we work over the normalized computed StyleMap (CSS longhands), so the pattern
802
- * recognizes the `width === height` shape in the BASE condition and rebuilds the element's class
803
- * StyleMap with a single `size` declaration (the resolver reverse-emits the concrete `size-*` token
804
- * at codegen). Both longhands are removed and replaced by the merged `size` decl, so the rewrite is
805
- * idempotent — once collapsed there is no `width`+`height` pair left to re-match.
806
- *
807
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
808
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
809
- * HTML never blocks a class-only rewrite); the
810
- * `rewriteClasses` recipe rebuilds the class StyleMap, returning `null` (decline) unless the BASE
811
- * width/height are equal, concrete, and share an `!important` flag.
554
+ * Flatten a do-nothing inline `<span>` wrapper into its sole element child, folding any inheritable
555
+ * styles down first so inherited values survive the box removal.
812
556
  */
813
- /** Fold equal `width`/`height` into the `size-*` utility. */
814
- declare const sizeShorthand: AuthoredPattern<Captures>;
557
+ declare const redundantInlineWrapper: AuthoredPattern<Captures>;
815
558
 
816
559
  /**
817
560
  * AUTO-GENERATED by `scripts/gen-registry.mjs` — DO NOT EDIT BY HAND.
818
561
  *
819
562
  * Regenerate with `npm run generate` (also runs automatically before build/typecheck/test).
820
- * Patterns are discovered by the `*.pattern.ts` file convention under `src/library/flatten`
821
- * and `src/library/compress`; the array below is sorted flatten-before-compress.
563
+ * Patterns are discovered by the recursive `*.pattern.ts` file convention (organized into DOMAIN
564
+ * folders under `src/library/`); the array below is sorted flatten-before-compress.
822
565
  */
823
566
 
824
567
  /** Every built-in pattern, in registration order (flatten patterns before compress). */
@@ -866,6 +609,12 @@ interface ResolvedDomflaxOptions {
866
609
  interface DomflaxTransformResult {
867
610
  readonly code: string;
868
611
  readonly map: EncodedSourceMap | null;
612
+ /**
613
+ * Per-file optimization delta (nodes removed / classes saved / bytes saved). Zeroed for
614
+ * unsupported or unchanged files. Consumed by the build adapters to accumulate the build-end
615
+ * {@link renderSummary summary}.
616
+ */
617
+ readonly stats: FileStatDelta;
869
618
  }
870
619
  /**
871
620
  * A configured domflax engine. Holds the wired core {@link Pipeline}, the passthrough
@@ -897,6 +646,12 @@ interface DomflaxVitePlugin {
897
646
  readonly enforce: 'pre';
898
647
  /** Vite's per-file source hook. Fully synchronous and browser-free. */
899
648
  transform(code: string, id: string): DomflaxTransformResult | null;
649
+ /** Vite build-start hook — resets the per-build summary accumulator (watch/serve safe). */
650
+ buildStart(): void;
651
+ /** Vite build-end hook — prints the aggregate {@link renderSummary} once (if anything changed). */
652
+ buildEnd(): void;
653
+ /** Vite close-bundle hook — prints the summary as a backstop if `buildEnd` did not fire. */
654
+ closeBundle(): void;
900
655
  }
901
656
  /**
902
657
  * Vite adapter. Returns a real Vite `Plugin` (`enforce: 'pre'`) whose `transform` runs the domflax
@@ -917,6 +672,12 @@ interface DomflaxWebpackModuleHost {
917
672
  module?: {
918
673
  rules?: unknown[];
919
674
  };
675
+ /** webpack's plugin list (present on both a real `Compiler.options` and Next's bare config). */
676
+ plugins?: unknown[];
677
+ }
678
+ /** A tappable webpack hook (only the `tap` arm domflax uses). */
679
+ interface DomflaxWebpackHook {
680
+ tap(name: string, fn: (arg: unknown) => void): void;
920
681
  }
921
682
  /**
922
683
  * Minimal webpack-compiler shape. Declared locally so this adapter does NOT depend on `webpack`'s
@@ -928,6 +689,10 @@ interface DomflaxWebpackModuleHost {
928
689
  */
929
690
  interface DomflaxWebpackCompiler extends DomflaxWebpackModuleHost {
930
691
  options?: DomflaxWebpackModuleHost;
692
+ /** Present only on a REAL webpack `Compiler` (not on Next's bare config). Used for the summary. */
693
+ hooks?: {
694
+ done?: DomflaxWebpackHook;
695
+ };
931
696
  }
932
697
  /**
933
698
  * Minimal webpack-plugin shape. `apply(compiler)` is the webpack plugin entry point.
@@ -936,30 +701,6 @@ interface DomflaxWebpackPlugin {
936
701
  readonly name: string;
937
702
  apply(compiler: DomflaxWebpackCompiler): void;
938
703
  }
939
- /**
940
- * webpack adapter (also the Next.js path). Returns a plugin whose `apply(compiler)` injects a
941
- * pre-enforced `module.rule` that invokes the domflax {@link ./webpack-loader loader} on every
942
- * `.jsx`/`.tsx` module. The loader runs the SAME lazy engine as {@link createDomflax} (no eager
943
- * Tailwind/postcss load).
944
- *
945
- * Next.js wiring (`next.config.js`) — Next exposes the underlying webpack config via `webpack(config)`:
946
- * ```js
947
- * // next.config.js
948
- * const domflax = require('domflax');
949
- * module.exports = {
950
- * webpack(config) {
951
- * domflax.webpack({ provider: 'tailwind' }).apply(config);
952
- * return config;
953
- * },
954
- * };
955
- * ```
956
- * `apply(compiler)` is intentionally duck-typed on `compiler.options.module.rules`, so it accepts
957
- * both a real webpack `Compiler` and the bare `config` object Next.js hands you.
958
- *
959
- * Caveat: this targets the webpack builder only. **Turbopack is not yet supported** — it does not
960
- * accept arbitrary webpack loaders, so the `next.config.js` wiring above is a no-op under
961
- * `next dev --turbopack`. Run domflax through the webpack builder until Turbopack exposes a loader API.
962
- */
963
704
  declare function webpack(options?: DomflaxOptions): DomflaxWebpackPlugin;
964
705
  /**
965
706
  * The default-export namespace. Exposes the build adapters and the programmatic factory as an OBJECT
@@ -975,4 +716,4 @@ interface DomflaxDefault {
975
716
  /** Default export: an object exposing `vite`, `webpack`, and the programmatic `createDomflax`. */
976
717
  declare const domflax: DomflaxDefault;
977
718
 
978
- export { ApplyContext, type ApplyOutcome, ApplyResult, Captures, DEFAULT_FIXPOINT, Diagnostic, type Domflax, type DomflaxDefault, type DomflaxOptions, type DomflaxProvider, type DomflaxTransformResult, type DomflaxVitePlugin, type DomflaxWebpackCompiler, type DomflaxWebpackPlugin, EncodedSourceMap, FixpointConfig, type FlattenClass, type FlattenClassification, type FlattenVerdict, HaltReason, IRDocument, IRNodeId, MatchContext, PHASE_ORDER, Pass, PassManager, PassPhase, Pattern, type PhaseLoopState, PhaseRunResult, Pipeline, type ResolvedDomflaxOptions, RewriteFactory, RewriteGroup, RewriteOp, RewriteOpDraft, type RunState, SafetyLevel, SelectorIndex, StyleNormalizer, StyleResolver, SyntheticSink, applyGroups, applyOps, borderRadiusShorthand, borderShorthand, buildMatchContext, buildSelectorIndex, builtinPatterns, classifyFlattenOps, cloneDocument, createDomflax, createNullResolver, createNullSelectorIndex, createPassManager, createPipeline, createRewriteFactory, createSyntheticSink, dedupeClasses, domflax as default, displayContentsWrapper, docFingerprint, emptyStyleDiv, evaluateElement, finalizePhase, flattenVerdict, flattenWouldDropStyle, flexCenterWrapper, gapShorthand, inlineFlexCenterWrapper, insetShorthand, marginShorthand, nestedFlexMerge, nestedGridMerge, overflowShorthand, overscrollBehaviorShorthand, paddingShorthand, passthroughWrapper, patternsForPhase, placeShorthand, redundantFragment, redundantInlineWrapper, revertDiagnostic, runPasses, scrollMarginShorthand, scrollPaddingShorthand, sizeShorthand, stampOrigin, syncClassesFromComputed, vite, webpack };
719
+ export { ApplyContext, type ApplyOutcome, ApplyResult, Captures, type CoverClass, DEFAULT_FIXPOINT, DEFAULT_MAX_UNIVERSE, Diagnostic, type Domflax, type DomflaxDefault, type DomflaxOptions, type DomflaxProvider, type DomflaxTransformResult, type DomflaxVitePlugin, type DomflaxWebpackCompiler, type DomflaxWebpackPlugin, EncodedSourceMap, FixpointConfig, type FlattenClass, type FlattenClassification, type FlattenVerdict, HaltReason, IRDocument, IRNodeId, MatchContext, type MinCoverOptions, PHASE_ORDER, Pass, PassManager, PassPhase, Pattern, type PhaseLoopState, PhaseRunResult, Pipeline, type ResolvedDomflaxOptions, RewriteFactory, RewriteGroup, RewriteOp, RewriteOpDraft, type RunState, SafetyLevel, SelectorIndex, StyleMap, StyleNormalizer, StyleResolver, SyntheticSink, applyGroups, applyOps, buildMatchContext, buildSelectorIndex, builtinPatterns, classifyFlattenOps, cloneDocument, createDomflax, createNullResolver, createNullSelectorIndex, createPassManager, createPipeline, createRewriteFactory, createSyntheticSink, domflax as default, displayContentsWrapper, docFingerprint, emptyStyleDiv, evaluateElement, finalizePhase, flattenVerdict, flattenWouldDropStyle, flexCenterWrapper, gridCenterWrapper, inheritedOnlyWrapper, minStringCover, passthroughWrapper, patternsForPhase, redundantFragment, redundantInlineWrapper, revertDiagnostic, runPasses, stampOrigin, styleMapTuples, syncClassesFromComputed, tupleKey, vite, webpack };