domflax 0.1.2 → 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 (49) hide show
  1. package/README.md +66 -31
  2. package/dist/chunk-EYQXQQQH.js +336 -0
  3. package/dist/chunk-EYQXQQQH.js.map +1 -0
  4. package/dist/{chunk-DNHOGPYV.js → chunk-FPT4EJ6Q.js} +1100 -1551
  5. package/dist/chunk-FPT4EJ6Q.js.map +1 -0
  6. package/dist/chunk-JBM3MJRM.js +382 -0
  7. package/dist/chunk-JBM3MJRM.js.map +1 -0
  8. package/dist/{chunk-DWLB7FRR.js → chunk-TTJEXWAC.js} +322 -9
  9. package/dist/chunk-TTJEXWAC.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 +3010 -2789
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js +268 -232
  15. package/dist/cli.js.map +1 -1
  16. package/dist/index.cjs +1684 -1649
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +255 -498
  19. package/dist/index.d.ts +255 -498
  20. package/dist/index.js +17 -37
  21. package/dist/{pattern-F5xBtIE-.d.cts → pattern-DotR_dHs.d.cts} +1 -1
  22. package/dist/pattern-kit.cjs +60 -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/{pattern-CV607P87.d.ts → pattern-urm5uuwj.d.ts} +1 -1
  28. package/dist/{resolve-ops-DIwEelH-.d.ts → resolve-ops-D8aQina5.d.cts} +20 -0
  29. package/dist/{resolve-ops-DIwEelH-.d.cts → resolve-ops-D8aQina5.d.ts} +20 -0
  30. package/dist/verify.d.cts +1 -1
  31. package/dist/verify.d.ts +1 -1
  32. package/dist/verify.js +1 -1
  33. package/dist/webpack-loader.cjs +1615 -1633
  34. package/dist/webpack-loader.cjs.map +1 -1
  35. package/dist/webpack-loader.d.cts +8 -2
  36. package/dist/webpack-loader.d.ts +8 -2
  37. package/dist/webpack-loader.js +8 -5
  38. package/dist/webpack-loader.js.map +1 -1
  39. package/dist/worker.cjs +5337 -0
  40. package/dist/worker.cjs.map +1 -0
  41. package/dist/worker.d.cts +2 -0
  42. package/dist/worker.d.ts +2 -0
  43. package/dist/worker.js +72 -0
  44. package/dist/worker.js.map +1 -0
  45. package/package.json +4 -2
  46. package/dist/chunk-DNHOGPYV.js.map +0 -1
  47. package/dist/chunk-DOQEBGWB.js +0 -188
  48. package/dist/chunk-DOQEBGWB.js.map +0 -1
  49. package/dist/chunk-DWLB7FRR.js.map +0 -1
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-DIwEelH-.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-DIwEelH-.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-F5xBtIE-.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-F5xBtIE-.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`).
@@ -201,13 +201,29 @@ declare function createPipeline(): Pipeline;
201
201
  * orchestrator (the `domflax` meta package, `@domflax/cli`, and the pattern auto-test harness) so
202
202
  * their pipelines cannot diverge.
203
203
  *
204
- * ## REPLACE, not append
204
+ * ## Only STYLE-DIRTY elements are re-emitted (never inflate a bystander)
205
205
  *
206
- * For every TOUCHED, rewritable (non-opaque, non-dynamic) element we ask the resolver for the
207
- * MINIMAL class set reproducing the element's FULL computed style (`resolver.emit(el.computed)`),
208
- * then REPLACE the element's static tokens with it rather than appending. Replacing is what lets
209
- * a compress pass actually shorten output: `px-4 py-4` collapses to `p-4`, equal `w/h` to `size-*`,
210
- * the four insets to `inset-0`, and fully-overridden duplicates simply disappear.
206
+ * Reverse-emit runs ONLY on elements a pass actually rewrote the computed style of — `meta.styleDirty`
207
+ * (a `setClassList`, a `mergeStyle` onto it, or an inherited fold into it). An element that was merely
208
+ * `touched` as a STRUCTURAL BYSTANDER a child was flattened/unwrapped, a sibling merged/moved, a
209
+ * node inserted next to it never has its own computed changed, so its `class` attribute is left
210
+ * BYTE-FOR-BYTE IDENTICAL. This is what stops a real custom-CSS site from INFLATING: an unchanged
211
+ * `<div class="product-art">` can no longer gain a redundant `.bg-cream-deep` just because an inert
212
+ * child next to it was flattened.
213
+ *
214
+ * ## REPLACE, not append — with retained-class coverage SUBTRACTED
215
+ *
216
+ * For every style-dirty, rewritable (non-opaque, non-dynamic) element we ask the resolver for the
217
+ * MINIMAL class set reproducing the element's computed style, then REPLACE the element's droppable
218
+ * tokens with it — rather than appending. Replacing is what lets a compress pass actually shorten
219
+ * output: `px-4 py-4` collapses to `p-4`, equal `w/h` to `size-*`, the four insets to `inset-0`, and
220
+ * fully-overridden duplicates simply disappear.
221
+ *
222
+ * Crucially, before choosing what to emit we SUBTRACT the style already supplied by the element's
223
+ * RETAINED (kept, non-droppable) classes: we emit only for the RESIDUAL declarations those classes do
224
+ * not already reproduce. So reverse-emit can never materialize a utility for a property a semantic
225
+ * class the element keeps already sets (the `.product-art` background is never re-added as
226
+ * `.bg-cream-deep`). Output therefore never grows with a class whose contribution is already covered.
211
227
  *
212
228
  * ## Droppability gate (never lose a load-bearing class)
213
229
  *
@@ -215,70 +231,127 @@ declare function createPipeline(): Pipeline;
215
231
  * plain, resolver-owned utility whose entire contribution is reproducible from `computed`. Tokens
216
232
  * that are unknown to the resolver, opaque (combinator/at-rule utilities whose effect never folds
217
233
  * onto the element's own box), variant-bound, or referenced by a custom-CSS selector are NOT
218
- * droppable and are preserved verbatim. As a safety net, if `emit` produces nothing at all we leave
219
- * the element's tokens untouched (a resolver that failed to load must never erase classes).
234
+ * droppable and are preserved verbatim. As a safety net, if the residual `emit` produces nothing at
235
+ * all we leave the element's tokens untouched (a resolver that failed to load must never erase
236
+ * classes).
220
237
  */
221
238
 
222
239
  /**
223
- * Fold every TOUCHED, rewritable element's optimized computed style back into the MINIMAL static
224
- * 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.
225
255
  */
226
256
  declare function syncClassesFromComputed(doc: IRDocument, resolver: StyleResolver, norm: StyleNormalizer): void;
227
257
 
228
258
  /**
229
- * @domflax/patternsflatten pattern: `display-contents-wrapper`.
259
+ * @domflax/corethe general COMPRESS ENGINE: a minimal-string exact-cover solver.
230
260
  *
231
- * Collapses a wrapper that has explicitly opted OUT of generating a box:
261
+ * ## What it replaces
232
262
  *
233
- * <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).
234
268
  *
235
- * `display:contents` makes an element generate NO box of its own its children render exactly as if
236
- * they were direct children of the element's parent. A `display:contents` wrapper with a single
237
- * element child is therefore already a layout passthrough: it contributes nothing to flow, paint
238
- * (a contents box paints nothing), formatting, stacking, or containing-block resolution. Removing it
239
- * and hoisting the child produces a tree that is layout-identical — the wrapper's only remaining
240
- * 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)
241
270
  *
242
- * This is the safest possible wrapper-elimination: the box being removed provably did not exist.
243
- * The opacity-barrier + selector-safety guards (ref/handlers/dynamic-children/raw-html/combinator/
244
- * reparent-impact) are auto-applied for every `flatten/*` pattern; the `where` predicates add the
245
- * passthrough-specific requirements (no own attrs / dynamic-or-spread classes, no `var()` coupling,
246
- * 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.
247
285
  *
248
- * (Chosen as the safe variant of the size-hoisting "full-size passthrough" idea: hoisting an explicit
249
- * `width/height:100%` onto a child is only sound when the child is block-level and unsized, which is
250
- * 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.
251
290
  */
291
+
252
292
  /**
253
- * Flatten a `display:contents` wrapper (a box that generates no box) into its sole element child,
254
- * 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.
255
295
  */
256
- declare const displayContentsWrapper: AuthoredPattern<Captures>;
257
-
296
+ declare function tupleKey(condition: string, property: string, value: string, important: boolean): string;
258
297
  /**
259
- * @domflax/patterns flatten pattern: `empty-style-div`.
260
- *
261
- * Collapses the most common piece of structural noise of all: a `<div>` whose ONLY role is to wrap
262
- * a single child while contributing nothing to layout or paint —
263
- *
264
- * <div><Child/></div> (no styles at all)
265
- * <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`.
266
319
  *
267
- * Such a div is layout-neutral: it is a plain block box with no own visual style, establishes no
268
- * box / formatting / stacking context, is not a containing block, and declares no custom properties
269
- * a descendant might read. Its box is therefore indistinguishable from "not being there", so it can
270
- * 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 `[]`.
271
323
  *
272
- * Authored with the declarative {@link definePattern} API. The opacity-barrier + selector-safety
273
- * guards (ref/handlers/dynamic-children/raw-html/combinator/reparent-impact) are applied
274
- * automatically for every `flatten/*` pattern; the `where` predicates below add the LAYOUT-neutrality
275
- * requirements specific to this pattern (no non-block display, no box/formatting/stacking context, no
276
- * containing block, no custom-property coupling, no structural-pseudo targeting).
324
+ * Pure and provider-agnostic: the caller performs the re-resolve correctness backstop.
277
325
  */
326
+ declare function minStringCover(universe: readonly string[], vocabulary: Iterable<CoverClass>, options?: MinCoverOptions): readonly string[] | null;
327
+
278
328
  /**
279
- * 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
+ * ```
280
345
  */
281
- 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
+ }
282
355
 
283
356
  /**
284
357
  * @domflax/patterns — flatten pattern: `flex-center-wrapper`.
@@ -303,108 +376,6 @@ declare const emptyStyleDiv: AuthoredPattern<Captures>;
303
376
  */
304
377
  declare const flexCenterWrapper: AuthoredPattern<Captures>;
305
378
 
306
- /**
307
- * @domflax/patterns — flatten pattern: `inline-flex-center-wrapper`.
308
- *
309
- * Collapses the inline-flex flavour of the "centering wrapper" idiom
310
- *
311
- * <div style="display:inline-flex; align-items:center; justify-content:center"><Child/></div>
312
- *
313
- * into its sole child, pushing the centering intent down onto the child as `place-self: center`.
314
- * Like its block-level `flex-center-wrapper` sibling the wrapper only exists to center one element;
315
- * once `place-self:center` lives on the child the wrapper is pure structural noise and can go.
316
- *
317
- * Authored with the declarative {@link definePattern} API: the match is the inline-flex-centering
318
- * computed-style signature on a single-element-child `<div>` that paints nothing of its own; the
319
- * recipe folds inheritable styles onto the child, grants it `place-self:center`, then unwraps the
320
- * wrapper (id-preserving). The opacity-barrier + selector-safety guards are applied automatically by
321
- * the `definePattern` factory for every `flatten/*` pattern.
322
- */
323
- /**
324
- * Flatten an inline-flex-centering `<div>` wrapper into its sole element child, granting the child
325
- * `place-self:center`.
326
- */
327
- declare const inlineFlexCenterWrapper: AuthoredPattern<Captures>;
328
-
329
- /**
330
- * @domflax/patterns — flatten pattern: `nested-flex-merge`.
331
- *
332
- * Collapses a redundant nesting of two flex containers
333
- *
334
- * <div style="display:flex; align-items:center; gap:8px">
335
- * <div style="display:flex; flex-direction:column"> … </div>
336
- * </div>
337
- *
338
- * where the OUTER flex container's sole element child is ITSELF a flex container, into a single
339
- * flex container that carries the union of both elements' flex declarations. The outer wrapper's
340
- * box is then structural noise (it paints nothing and only establishes a flex context that the
341
- * merged child now also establishes), so it is removed.
342
- *
343
- * Authored with the declarative {@link pattern} API: the match is a flex `<div>` with a single
344
- * element child painting nothing of its own (auto-guarded against opacity barriers / combinator
345
- * targeting like every `flatten/*` pattern). The value-relational reasoning — the child must also
346
- * be a (non-combinator) flex container, the wrapper must carry only transferable flex/inheritable
347
- * declarations, and the two must not conflict on any shared flex property — lives in the `rewrite`
348
- * op-draft factory escape hatch, which folds inherited styles, transfers the wrapper's flex
349
- * declarations onto the child (target-wins), then unwraps the wrapper.
350
- */
351
- /**
352
- * Flatten a flex container whose sole child is a compatible flex container into a single container.
353
- */
354
- declare const nestedFlexMerge: AuthoredPattern<Captures>;
355
-
356
- /**
357
- * @domflax/patterns — flatten pattern: `nested-grid-merge`.
358
- *
359
- * Collapses a redundant nesting of two grid containers
360
- *
361
- * <div style="display:grid; gap:8px">
362
- * <div style="display:grid; grid-template-columns:1fr 1fr"> … </div>
363
- * </div>
364
- *
365
- * where the OUTER grid container's sole element child is ITSELF a grid container, into a single grid
366
- * container carrying the union of both elements' grid declarations. The outer wrapper's box is then
367
- * structural noise (it paints nothing and only establishes a grid context the merged child now also
368
- * establishes), so it is removed.
369
- *
370
- * This is the grid analogue of `nested-flex-merge`. The declarative match is a grid `<div>` with a
371
- * single element child painting nothing of its own (auto-guarded against opacity barriers / combinator
372
- * targeting like every `flatten/*` pattern). The value-relational reasoning — the child must also be a
373
- * (non-combinator) grid container, the wrapper must carry only transferable grid/inheritable
374
- * declarations, and the two must not conflict on any shared grid property — lives in the `rewrite`
375
- * op-draft factory escape hatch, which folds inherited styles, transfers the wrapper's grid
376
- * declarations onto the child (target-wins), then unwraps the wrapper.
377
- */
378
- /**
379
- * Flatten a grid container whose sole child is a compatible grid container into a single container.
380
- */
381
- declare const nestedGridMerge: AuthoredPattern<Captures>;
382
-
383
- /**
384
- * @domflax/patterns — flatten pattern: `passthrough-wrapper`.
385
- *
386
- * Collapses a purely-structural wrapper that exists for no reason at all:
387
- *
388
- * <div><Child/></div>
389
- *
390
- * The wrapper paints nothing, establishes no box / formatting / stacking context, carries no
391
- * attributes beyond an (optional) inert class, holds exactly one element child, and is free of every
392
- * opacity barrier (ref / event-handlers / dynamic children / dangerous html / spread / component).
393
- * Such a `<div>` is pure DOM noise: removing it and hoisting the child is invisible to both paint
394
- * and layout.
395
- *
396
- * Authored with the declarative {@link definePattern} API. The opacity-barrier + selector-safety
397
- * guards (ref/handlers/dynamic-children/raw-html/combinator/reparent-impact) are applied
398
- * automatically for every `flatten/*` pattern; the `where` predicates add the passthrough-specific
399
- * requirements (no box/formatting/stacking context, no own attrs, no dynamic/spread classes, not a
400
- * component, not a structural-pseudo subject).
401
- */
402
- /**
403
- * Flatten a do-nothing `<div>` wrapper into its sole element child, folding any inheritable styles
404
- * down first so inherited values survive the box removal.
405
- */
406
- declare const passthroughWrapper: AuthoredPattern<Captures>;
407
-
408
379
  /**
409
380
  * @domflax/patterns — flatten pattern: `redundant-fragment`.
410
381
  *
@@ -430,378 +401,167 @@ declare const passthroughWrapper: AuthoredPattern<Captures>;
430
401
  declare const redundantFragment: AuthoredPattern<Captures>;
431
402
 
432
403
  /**
433
- * @domflax/patterns — flatten pattern: `redundant-inline-wrapper`.
434
- *
435
- * Collapses a purely-structural INLINE wrapper:
436
- *
437
- * <span><Child/></span> (display:inline, no own style)
438
- *
439
- * An inline `<span>` that paints nothing, establishes no box / formatting / stacking context, carries
440
- * no attributes beyond an (optional) inert class, declares no custom properties, and holds exactly one
441
- * element child is pure inline noise. An empty inline box merely wraps its child's box; removing it and
442
- * hoisting the child leaves both paint and layout untouched (the surviving child folds the inheritable
443
- * declarations the span carried).
444
- *
445
- * This is the inline sibling of `passthrough-wrapper` (which targets `<div>`): the same opacity-barrier
446
- * + selector-safety guards are auto-applied by the `pattern()` factory for every `flatten/*` pattern;
447
- * the `where` predicates add the inline-passthrough requirements (display must be the inline default,
448
- * no box/formatting/stacking context or var coupling, no own attrs / dynamic-or-spread classes, not a
449
- * component, not a structural-pseudo subject).
450
- */
451
- /**
452
- * Flatten a do-nothing inline `<span>` wrapper into its sole element child, folding any inheritable
453
- * styles down first so inherited values survive the box removal.
454
- */
455
- declare const redundantInlineWrapper: AuthoredPattern<Captures>;
456
-
457
- /**
458
- * @domflax/patterns — compress pattern: `border-radius-shorthand`.
459
- *
460
- * Collapses an element whose four corner radii are expressed as separate longhand declarations and
461
- * are ALL EQUAL into the single CSS `border-radius` shorthand:
462
- *
463
- * border-top-left-radius:0.5rem; border-top-right-radius:0.5rem;
464
- * border-bottom-right-radius:0.5rem; border-bottom-left-radius:0.5rem
465
- * ⇒ border-radius:0.5rem (Tailwind `rounded-lg`)
466
- *
467
- * The IR's computed StyleMap keeps each corner as its own longhand (Tailwind's `rounded-tl-*` /
468
- * `rounded-tr-*` / … each resolve to one corner property). This pass runs the collapse in reverse on
469
- * the computed map ONLY when all four corners share one value — the single case that maps cleanly to
470
- * a single Tailwind utility (the CSS 2-value `border-radius` form is DIAGONAL, which has no clean
471
- * `rounded-*` edge utility, so per-corner differences are intentionally left alone). Rebuilding the
472
- * map with one `border-radius` decl lets the minimizing reverse-emit pick the single `rounded-*`
473
- * token covering all four corners instead of two edge tokens.
474
- *
475
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
476
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
477
- * HTML never blocks a class-only rewrite); the
478
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the four corners are
479
- * present, concrete, equal, and share an `!important` flag.
480
- */
481
- /** Fold four equal corner radii into the single `border-radius` shorthand. */
482
- declare const borderRadiusShorthand: AuthoredPattern<Captures>;
483
-
484
- /**
485
- * @domflax/patterns — compress pattern: `border-shorthand`.
486
- *
487
- * Collapses an element whose four border-side WIDTHS are expressed as separate longhand declarations
488
- * back into the shortest equivalent `border-width` shorthand:
489
- *
490
- * border-top-width:2px; border-right-width:2px; border-bottom-width:2px; border-left-width:2px
491
- * ⇒ border-width:2px (Tailwind `border-2`)
492
- *
493
- * border-top-width:2px; border-bottom-width:2px; border-left-width:4px; border-right-width:4px
494
- * ⇒ border-width:2px 4px (Tailwind `border-y-2 border-x-4`)
495
- *
496
- * Tailwind's per-side / per-axis width utilities (`border-t-*`, `border-x-*`, …) each resolve to the
497
- * matching `border-*-width` longhand(s); the shared normalizer keeps them longhand. This pass runs
498
- * the expansion in reverse on the computed map ONLY when the four widths fold cleanly into a 1- or
499
- * 2-value form — i.e. `top===bottom` AND `left===right`. Rebuilding the map with one `border-width`
500
- * shorthand lets the minimizing reverse-emit pick the single/paired utility (`border-2`, or
501
- * `border-x-* border-y-*`) instead of four per-side tokens. Only WIDTH is folded — border style and
502
- * color are independent longhands the resolver carries separately, so this never disturbs them.
503
- *
504
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
505
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
506
- * HTML never blocks a class-only rewrite); the `rewriteClasses`
507
- * recipe rebuilds the class StyleMap, declining (`null`) unless the four widths fold cleanly.
508
- */
509
- /** Compress an element's four equal/paired border-width longhands into the shortest shorthand. */
510
- declare const borderShorthand: AuthoredPattern<Captures>;
511
-
512
- /**
513
- * @domflax/patterns — compress pattern: `dedupe-classes`.
404
+ * @domflax/patterns — flatten pattern: `grid-center-wrapper`.
514
405
  *
515
- * Removes duplicate / fully-overridden class tokens that resolve to the same property where a
516
- * LATER token wins, leaving the minimal set of tokens with an IDENTICAL computed style. The
517
- * canonical case:
406
+ * Collapses the grid flavour of the "centering wrapper" idiom
518
407
  *
519
- * <p class="text-sm text-lg">…</p> → <p class="text-lg">…</p>
408
+ * <div style="display:grid; align-items:center; justify-content:center"><Child/></div>
520
409
  *
521
- * Both `text-sm` and `text-lg` set `font-size`; resolution already made `text-lg` win, so the
522
- * computed `font-size` is `text-lg`'s value and `text-sm` contributes NOTHING to the final
523
- * computed style. The earlier token is pure noise and can be dropped without changing a pixel.
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.
524
413
  *
525
- * How redundancy is detected (purely from the already-resolved, normalized computed StyleMap):
526
- * every declaration carries provenance `origin` (the winning token) and `shadowed`
527
- * (the tokens it overrode);
528
- * a class token is FULLY OVERRIDDEN iff it appears in some declaration's `shadowed` list but
529
- * is NOT the winning `origin` of any declaration across ANY style condition. Such a token can
530
- * be deleted with zero effect on the computed style.
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.
531
418
  *
532
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards a dynamic or opaque class list
533
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
534
- * HTML never blocks a class-only rewrite); the `dropClasses` recipe returns the set of
535
- * fully-overridden, resolver-droppable tokens to delete (their `shadowed` provenance is pruned
536
- * automatically before the minimal class StyleMap is re-installed).
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).
537
423
  */
538
424
  /**
539
- * Collapse a class list to the minimal token set that yields an identical computed style, by
540
- * dropping tokens whose declarations are fully overridden by later tokens.
541
- */
542
- declare const dedupeClasses: AuthoredPattern<Captures>;
543
-
544
- /**
545
- * @domflax/patterns — compress pattern: `gap-shorthand`.
546
- *
547
- * Collapses an element whose grid/flex gutters are expressed as two equal axis longhands back into
548
- * the single `gap` shorthand:
549
- *
550
- * row-gap:16px; column-gap:16px ⇒ gap:16px (Tailwind `gap-x-4 gap-y-4` → `gap-4`)
551
- *
552
- * The IR's computed StyleMap is canonically LONGHAND (the shared normalizer expands the `gap`
553
- * shorthand into `row-gap` + `column-gap` at parse time). This pass runs the expansion in reverse on
554
- * the computed map ONLY when both axes carry the SAME value and `!important` flag — i.e. when the two
555
- * gutters genuinely fold into a single-value `gap`. When the axes differ it declines, leaving the two
556
- * longhands verbatim (an asymmetric gutter has no equivalent single-value shorthand).
557
- *
558
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
559
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
560
- * HTML never blocks a class-only rewrite); the
561
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the two axis gaps
562
- * are present, equal, and share an `!important` flag.
563
- */
564
- /** Fold an equal `row-gap`/`column-gap` pair into the single `gap` shorthand. */
565
- declare const gapShorthand: AuthoredPattern<Captures>;
566
-
567
- /**
568
- * @domflax/patterns — compress pattern: `inset-shorthand`.
569
- *
570
- * Recompacts the four physical inset longhands (`top`/`right`/`bottom`/`left`) on an element's
571
- * computed style back into the tightest CSS shorthand the values allow:
572
- *
573
- * • all four equal → `inset: <v>`
574
- * • top == bottom (a matching pair) → `inset-block: <v>` (Tailwind `inset-y-*`)
575
- * • left == right (a matching pair) → `inset-inline: <v>` (Tailwind `inset-x-*`)
576
- *
577
- * The two axis collapses are independent: an element whose `top == bottom` but `left != right`
578
- * collapses only the block axis and keeps the `left`/`right` longhands verbatim. When nothing
579
- * collapses (all four distinct, or fewer than a full pair present) the pattern declines.
580
- *
581
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
582
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
583
- * HTML never blocks a class-only rewrite); the `rewriteClasses` recipe rebuilds the class
584
- * StyleMap, declining (`null`) unless at least one inset axis collapses.
585
- */
586
- /**
587
- * Collapse equal/paired physical inset longhands into the `inset` / `inset-block` / `inset-inline`
588
- * 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`.
589
427
  */
590
- declare const insetShorthand: AuthoredPattern<Captures>;
428
+ declare const gridCenterWrapper: AuthoredPattern<Captures>;
591
429
 
592
430
  /**
593
- * @domflax/patterns — compress pattern: `margin-shorthand`.
594
- *
595
- * Collapses the four explicit margin longhands
431
+ * @domflax/patterns — flatten pattern: `display-contents-wrapper`.
596
432
  *
597
- * margin-top / margin-right / margin-bottom / margin-left
433
+ * Collapses a wrapper that has explicitly opted OUT of generating a box:
598
434
  *
599
- * back into a single CSS `margin` shorthand declaration on the SAME element (the margin analogue of
600
- * `padding-shorthand`, covering the `m` / `mx` / `my` collapse), choosing the shortest legal
601
- * 1–4-value form:
435
+ * <div style="display:contents"><Child/></div> → <Child/>
602
436
  *
603
- * all four equal → `margin: <v>` (the `m` case)
604
- * top==bottom and left==right → `margin: <y> <x>` (the `my`/`mx` case)
605
- * left==right (top!=bottom) → `margin: <t> <x> <b>`
606
- * 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.
607
443
  *
608
- * It is a pure representation change: the resolved box model is identical, only the declaration
609
- * 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).
610
449
  *
611
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
612
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
613
- * HTML never blocks a class-only rewrite); the `rewriteClasses` recipe rebuilds the class
614
- * StyleMap, declining (`null`) unless all four margin longhands are present with a uniform
615
- * (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.)
616
453
  */
617
454
  /**
618
- * 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.
619
457
  */
620
- declare const marginShorthand: AuthoredPattern<Captures>;
458
+ declare const displayContentsWrapper: AuthoredPattern<Captures>;
621
459
 
622
460
  /**
623
- * @domflax/patterns — compress pattern: `overflow-shorthand`.
461
+ * @domflax/patterns — flatten pattern: `empty-style-div`.
624
462
  *
625
- * Collapses an element whose two overflow axes are expressed as equal longhands back into the single
626
- * `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 —
627
465
  *
628
- * overflow-x:auto; overflow-y:auto ⇒ overflow:auto (Tailwind `overflow-x-auto overflow-y-auto`
629
- * `overflow-auto`)
466
+ * <div><Child/></div> (no styles at all)
467
+ * <div style="display:block"><Child/></div> (the default; still a no-op box)
630
468
  *
631
- * Unlike the box shorthands, the shared normalizer leaves `overflow-x` / `overflow-y` as independent
632
- * longhands (it does not synthesize an `overflow` shorthand), so an element styled with two equal
633
- * axis utilities keeps two separate declarations until this pass folds them. The fold runs ONLY when
634
- * both axes carry the SAME value and `!important` flag; an asymmetric pair (`overflow-x !==
635
- * 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.
636
473
  *
637
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
638
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
639
- * HTML never blocks a class-only rewrite); the
640
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless both overflow axes
641
- * 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).
642
479
  */
643
- /** Fold an equal `overflow-x`/`overflow-y` pair into the single `overflow` shorthand. */
644
- declare const overflowShorthand: AuthoredPattern<Captures>;
645
-
646
480
  /**
647
- * @domflax/patterns compress pattern: `overscroll-behavior-shorthand`.
648
- *
649
- * Collapses an element whose two overscroll-behavior axes are expressed as separate longhand
650
- * declarations and are EQUAL into the single CSS `overscroll-behavior` shorthand:
651
- *
652
- * overscroll-behavior-x:contain; overscroll-behavior-y:contain
653
- * ⇒ overscroll-behavior:contain (Tailwind `overscroll-x-contain overscroll-y-contain` → `overscroll-contain`)
654
- *
655
- * Tailwind's `overscroll-x-*` / `overscroll-y-*` utilities each resolve to the matching
656
- * `overscroll-behavior-{x,y}` axis longhand, and the shared normalizer keeps `overscroll-behavior`
657
- * un-expanded (it is NOT one of the box/gap shorthands the normalizer splits). So only the equal-axis
658
- * form maps cleanly to a single `overscroll-*` utility. This pass runs the collapse in reverse on the
659
- * computed map ONLY when both axes carry the SAME value and `!important` flag, replacing them with one
660
- * `overscroll-behavior` decl so the minimizing reverse-emit can pick a single `overscroll-*` token
661
- * instead of two axis tokens. When the axes differ it declines, leaving the two longhands verbatim.
662
- *
663
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
664
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
665
- * HTML never blocks a class-only rewrite); the
666
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless both axes are
667
- * present, concrete, equal, and share an `!important` flag.
481
+ * Flatten a layout-neutral, style-free `<div>` wrapper into its sole element child.
668
482
  */
669
- /** Fold an equal overscroll-behavior x/y pair into the single `overscroll-behavior` shorthand. */
670
- declare const overscrollBehaviorShorthand: AuthoredPattern<Captures>;
483
+ declare const emptyStyleDiv: AuthoredPattern<Captures>;
671
484
 
672
485
  /**
673
- * @domflax/patterns — compress pattern: `padding-shorthand`.
674
- *
675
- * Collapses an element whose four padding sides are expressed as separate longhand declarations
676
- * back into the shortest equivalent shorthand:
486
+ * @domflax/patterns — flatten pattern: `inherited-only-wrapper`.
677
487
  *
678
- * padding-top:16px; padding-right:16px; padding-bottom:16px; padding-left:16px
679
- * ⇒ padding:16px (Tailwind `p-4`)
488
+ * Collapses a paint-free wrapper whose ONLY own declarations are INHERITED properties
489
+ * (`text-align`, `color`, `font-*`, `line-height`, `letter-spacing`, `white-space`, …):
680
490
  *
681
- * padding-top:8px; padding-bottom:8px; padding-left:16px; padding-right:16px
682
- * ⇒ padding:8px 16px (Tailwind `px-4 py-2`)
491
+ * <div style="text-align:center"><Child/></div> → <Child style="text-align:center"/>
683
492
  *
684
- * The IR's computed StyleMap is canonically LONGHAND (the shared normalizer expands every box
685
- * shorthand at parse time). This pass runs the expansion in reverse on the computed map ONLY when
686
- * the four sides fold cleanly into a 1- or 2-value form i.e. `top===bottom` AND `left===right`.
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.
687
497
  *
688
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards a dynamic or opaque class list
689
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
690
- * HTML never blocks a class-only rewrite); the `rewriteClasses`
691
- * 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.
692
502
  */
693
503
  /**
694
- * 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).
695
506
  */
696
- declare const paddingShorthand: AuthoredPattern<Captures>;
507
+ declare const inheritedOnlyWrapper: AuthoredPattern<Captures>;
697
508
 
698
509
  /**
699
- * @domflax/patterns — compress pattern: `place-shorthand`.
510
+ * @domflax/patterns — flatten pattern: `passthrough-wrapper`.
700
511
  *
701
- * Recompacts the grid/flex alignment longhands on an element's computed style into the CSS `place-*`
702
- * shorthands whenever the two axes of a pair agree:
512
+ * Collapses a purely-structural wrapper that exists for no reason at all:
703
513
  *
704
- * • align-items == justify-items → `place-items: <v>` (Tailwind `items-* justify-items-*`)
705
- * • align-content == justify-content → `place-content: <v>` (Tailwind `content-* justify-*`)
514
+ * <div><Child/></div>
706
515
  *
707
- * The two collapses are INDEPENDENT: an element whose items pair agrees but whose content pair does
708
- * not collapses only `place-items` and keeps the content longhands verbatim. The shared normalizer
709
- * leaves all four alignment properties as independent longhands (it synthesizes no `place-*`
710
- * shorthand), so an element styled with two matching axis utilities keeps the longhands until this
711
- * 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.
712
521
  *
713
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
714
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
715
- * HTML never blocks a class-only rewrite); the
716
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless at least one
717
- * 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).
718
527
  */
719
- /** Fold matching align/justify pairs into the `place-items` / `place-content` shorthands. */
720
- declare const placeShorthand: AuthoredPattern<Captures>;
721
-
722
528
  /**
723
- * @domflax/patterns compress pattern: `scroll-margin-shorthand`.
724
- *
725
- * Collapses an element whose four scroll-margin sides are expressed as separate longhand
726
- * declarations and are ALL EQUAL into the single CSS `scroll-margin` shorthand:
727
- *
728
- * scroll-margin-top:1rem; scroll-margin-right:1rem;
729
- * scroll-margin-bottom:1rem; scroll-margin-left:1rem
730
- * ⇒ scroll-margin:1rem (Tailwind `scroll-m-4`)
731
- *
732
- * Tailwind's `scroll-mt-*` / `scroll-mx-*` / … utilities each resolve to the matching
733
- * `scroll-margin-*` longhand(s), and the shared normalizer keeps `scroll-margin` un-expanded (it is
734
- * NOT one of the box shorthands the normalizer splits). So only the all-equal (1-value) form maps
735
- * cleanly to a single `scroll-m-*` utility — the 2-value (`scroll-mx`/`scroll-my`) shape is left to
736
- * the resolver's own reverse-emit. This pass runs the collapse in reverse on the computed map ONLY
737
- * when all four sides share one value, replacing them with one `scroll-margin` decl so the minimizing
738
- * reverse-emit can pick a single `scroll-m-*` token instead of two axis tokens.
739
- *
740
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
741
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
742
- * HTML never blocks a class-only rewrite); the
743
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the four sides are
744
- * 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.
745
531
  */
746
- /** Fold four equal scroll-margin sides into the single `scroll-margin` shorthand. */
747
- declare const scrollMarginShorthand: AuthoredPattern<Captures>;
532
+ declare const passthroughWrapper: AuthoredPattern<Captures>;
748
533
 
749
534
  /**
750
- * @domflax/patterns — compress pattern: `scroll-padding-shorthand`.
535
+ * @domflax/patterns — flatten pattern: `redundant-inline-wrapper`.
751
536
  *
752
- * Collapses an element whose four scroll-padding sides are expressed as separate longhand
753
- * declarations and are ALL EQUAL into the single CSS `scroll-padding` shorthand:
537
+ * Collapses a purely-structural INLINE wrapper:
754
538
  *
755
- * scroll-padding-top:1rem; scroll-padding-right:1rem;
756
- * scroll-padding-bottom:1rem; scroll-padding-left:1rem
757
- * ⇒ scroll-padding:1rem (Tailwind `scroll-p-4`)
539
+ * <span><Child/></span> (display:inline, no own style)
758
540
  *
759
- * Tailwind's `scroll-pt-*` / `scroll-px-*` / utilities each resolve to the matching
760
- * `scroll-padding-*` longhand(s), and the shared normalizer keeps `scroll-padding` un-expanded (it is
761
- * NOT one of the box shorthands the normalizer splits). So only the all-equal (1-value) form maps
762
- * cleanly to a single `scroll-p-*` utility the 2-value (`scroll-px`/`scroll-py`) shape is left to
763
- * the resolver's own reverse-emit. This pass runs the collapse in reverse on the computed map ONLY
764
- * when all four sides share one value, replacing them with one `scroll-padding` decl so the minimizing
765
- * 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).
766
546
  *
767
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
768
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
769
- * HTML never blocks a class-only rewrite); the
770
- * `rewriteClasses` recipe rebuilds the class StyleMap, declining (`null`) unless the four sides are
771
- * 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).
772
552
  */
773
- /** Fold four equal scroll-padding sides into the single `scroll-padding` shorthand. */
774
- declare const scrollPaddingShorthand: AuthoredPattern<Captures>;
775
-
776
553
  /**
777
- * @domflax/patterns compress pattern: `size-shorthand`.
778
- *
779
- * Collapses an element whose computed `width` and `height` are EQUAL into the single Tailwind
780
- * `size-*` utility:
781
- *
782
- * <div style="width:1rem; height:1rem"/> → <div class="size-4"/>
783
- *
784
- * At the IR level we work over the normalized computed StyleMap (CSS longhands), so the pattern
785
- * recognizes the `width === height` shape in the BASE condition and rebuilds the element's class
786
- * StyleMap with a single `size` declaration (the resolver reverse-emits the concrete `size-*` token
787
- * at codegen). Both longhands are removed and replaced by the merged `size` decl, so the rewrite is
788
- * idempotent — once collapsed there is no `width`+`height` pair left to re-match.
789
- *
790
- * Authored with the declarative {@link pattern} API: `definePattern` auto-applies the compress safety guards — a dynamic or opaque class list
791
- * and combinator-subject selectors are excluded (a ref / event handler / dynamic child / dangerous
792
- * HTML never blocks a class-only rewrite); the
793
- * `rewriteClasses` recipe rebuilds the class StyleMap, returning `null` (decline) unless the BASE
794
- * 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.
795
556
  */
796
- /** Fold equal `width`/`height` into the `size-*` utility. */
797
- declare const sizeShorthand: AuthoredPattern<Captures>;
557
+ declare const redundantInlineWrapper: AuthoredPattern<Captures>;
798
558
 
799
559
  /**
800
560
  * AUTO-GENERATED by `scripts/gen-registry.mjs` — DO NOT EDIT BY HAND.
801
561
  *
802
562
  * Regenerate with `npm run generate` (also runs automatically before build/typecheck/test).
803
- * Patterns are discovered by the `*.pattern.ts` file convention under `src/library/flatten`
804
- * 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.
805
565
  */
806
566
 
807
567
  /** Every built-in pattern, in registration order (flatten patterns before compress). */
@@ -814,13 +574,12 @@ declare const builtinPatterns: readonly Pattern[];
814
574
  * `@domflax/patterns` library, then layers thin, framework-agnostic build adapters on top
815
575
  * (`vite()` / `webpack()`) plus a programmatic `createDomflax()` factory.
816
576
  *
817
- * Each adapter runs the SAME single-file engine as {@link createDomflax} (JSX/TSX frontend + lazy
818
- * Tailwind/CSS resolver → core pass manager → reverse-emit → JSX backend). The adapters are
577
+ * Each adapter runs the SAME single-file engine as {@link createDomflax} (JSX/TSX + HTML frontends +
578
+ * lazy Tailwind/CSS resolver → core pass manager → reverse-emit → surgical backend). The adapters are
819
579
  * structurally typed against their bundlers — they never hard-depend on `vite` or `webpack`.
820
580
  *
821
- * Future deps (intentionally NOT imported yet they land in a later stage):
822
- * - `@domflax/frontend-html` HTML / Astro-static frontend feeding the pipeline.
823
- * - `@domflax/backend-*` — additional surgical codegen backends.
581
+ * `.jsx`/`.tsx` route to `@domflax/frontend-jsx` (Babel); `.html`/`.htm` route to
582
+ * `@domflax/frontend-html` (parse5). Both emit via SURGICAL span edits over the original source.
824
583
  */
825
584
 
826
585
  /** How class names resolve to computed styles. */
@@ -850,6 +609,12 @@ interface ResolvedDomflaxOptions {
850
609
  interface DomflaxTransformResult {
851
610
  readonly code: string;
852
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;
853
618
  }
854
619
  /**
855
620
  * A configured domflax engine. Holds the wired core {@link Pipeline}, the passthrough
@@ -881,6 +646,12 @@ interface DomflaxVitePlugin {
881
646
  readonly enforce: 'pre';
882
647
  /** Vite's per-file source hook. Fully synchronous and browser-free. */
883
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;
884
655
  }
885
656
  /**
886
657
  * Vite adapter. Returns a real Vite `Plugin` (`enforce: 'pre'`) whose `transform` runs the domflax
@@ -901,6 +672,12 @@ interface DomflaxWebpackModuleHost {
901
672
  module?: {
902
673
  rules?: unknown[];
903
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;
904
681
  }
905
682
  /**
906
683
  * Minimal webpack-compiler shape. Declared locally so this adapter does NOT depend on `webpack`'s
@@ -912,6 +689,10 @@ interface DomflaxWebpackModuleHost {
912
689
  */
913
690
  interface DomflaxWebpackCompiler extends DomflaxWebpackModuleHost {
914
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
+ };
915
696
  }
916
697
  /**
917
698
  * Minimal webpack-plugin shape. `apply(compiler)` is the webpack plugin entry point.
@@ -920,30 +701,6 @@ interface DomflaxWebpackPlugin {
920
701
  readonly name: string;
921
702
  apply(compiler: DomflaxWebpackCompiler): void;
922
703
  }
923
- /**
924
- * webpack adapter (also the Next.js path). Returns a plugin whose `apply(compiler)` injects a
925
- * pre-enforced `module.rule` that invokes the domflax {@link ./webpack-loader loader} on every
926
- * `.jsx`/`.tsx` module. The loader runs the SAME lazy engine as {@link createDomflax} (no eager
927
- * Tailwind/postcss load).
928
- *
929
- * Next.js wiring (`next.config.js`) — Next exposes the underlying webpack config via `webpack(config)`:
930
- * ```js
931
- * // next.config.js
932
- * const domflax = require('domflax');
933
- * module.exports = {
934
- * webpack(config) {
935
- * domflax.webpack({ provider: 'tailwind' }).apply(config);
936
- * return config;
937
- * },
938
- * };
939
- * ```
940
- * `apply(compiler)` is intentionally duck-typed on `compiler.options.module.rules`, so it accepts
941
- * both a real webpack `Compiler` and the bare `config` object Next.js hands you.
942
- *
943
- * Caveat: this targets the webpack builder only. **Turbopack is not yet supported** — it does not
944
- * accept arbitrary webpack loaders, so the `next.config.js` wiring above is a no-op under
945
- * `next dev --turbopack`. Run domflax through the webpack builder until Turbopack exposes a loader API.
946
- */
947
704
  declare function webpack(options?: DomflaxOptions): DomflaxWebpackPlugin;
948
705
  /**
949
706
  * The default-export namespace. Exposes the build adapters and the programmatic factory as an OBJECT
@@ -959,4 +716,4 @@ interface DomflaxDefault {
959
716
  /** Default export: an object exposing `vite`, `webpack`, and the programmatic `createDomflax`. */
960
717
  declare const domflax: DomflaxDefault;
961
718
 
962
- 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 };