Revision history for CSS::Minifier

0.0.9  2026-06-06T05:43:28+03:00

    - Refactor: consolidate color/unit normalization helpers in Util.rakumod
      (NAMED-RE/HEX-TO-NAMED-RE, strip-zero-units, normalize-hex-value);
      $UNITS now derives from $LENGTH-UNITS via BEGIN block
    - Refactor: method cleanup — !same-selectors→eqv, !make-declaration helper,
      !url-quote→starts-with/ends-with, inline !decl-key, remove clear-comb-cache
      from parse, return-type annotations on Merging, rename $space→$brace-space
      (Writer), $p→$sel (Parser)
    - CQ: minor cleanup — comment $UNITS vs $LENGTH-UNITS asymmetry, replace
      magic 12 with anchored regex, remove redundant !apply-font-weight guard,
      fix !make-declaration indent, remove unused *%opts from minify()
    - Fix: parser edge cases — has-brace off-by-one and bare-/ double-increment,
      !parse-at-rule indent, '@' in extract-braceless respects $in-license,
      limit substr peek to 12 chars, typed $buf/$in-license
    - Fix: normalization correctness — hex shortening/folding in
      declarations-to-key, !normalize-quotes escaped \" protection,
      !strip-zero-units length-only guard, !url-quote-value quoted-paren fix,
      !url-quote-selector single-quote guard, rgb-values-to-hex float percentage
      and :i guard, empty background longhand guard, remove
      text-decoration-thickness/underline-offset from %COLOR-PROPS
    - Fix: dedup/merge — Dedup returns result instead of mutating param,
      bold/normal word boundaries (Bug 1), selector identity over .fc (Bug 2),
      revert-layer (Bug 3), strip-zero-units 0+ match (Bug 4),
      add default branch, align AtRule barrier check
    - Perf: fast-path guards on declarations-to-key/rgb-values-to-hex/
      strip-zero-units; combine grep+map in !merge-decls; remove 100KB
      comb-cached bypass so large files benefit from cache
    - Test: delete 01-basic (merged into golden), qqx→run, add comb-cache
      eviction, --output, !important precedence, empty-ruleset :sep tests
    - Fix: percentage rgb() now rounds (.round) instead of truncating
      (.Int) — 50% → #80 not #7F per CSS Color spec
    - Fix: selector comparison uses identity over .fc — class/ID
      selectors are case-sensitive per spec (Merging + Dedup)
    - Fix: CLI --level without value always errors (last-arg edge case)
    - Perf: gradual cache eviction (delete oldest 250 keys) instead
      of full hash flush; skip color-masks pass when color-names enabled
    - CQ: declarations-to-key skips normalization for --custom props
    - CQ: COMB-CACHE-MAX configurable via CSS_MINIFIER_CACHE_MAX env var
    - CQ: !assemble-background-multi uses manual max loop instead of
      %.values».elems intermediate arrays

0.0.8  2026-06-05T07:46:49+03:00

    - ~15× performance improvement: comb-cached .comb with binding
      (:=) at all call sites, skip-quoted copy elimination, .chars
      caching in hot loops
    - Color masking fixes: url() quarantine (prevents filename
      corruption), rgb()→named-color without :color-masks,
      %COLOR-PROPS fast-path skip, %NON-COLOR-PROPS font-family guard,
      custom property bypass, :i flag + .lc lookup in named-color
      alternation, leading-space rgb/rgba, hex→name word-boundary
      match, rgba 1.0 fix, CSS Color Level 4 space-separated rgb
    - Parser fixes: OOB edge cases in !extract-braceless,
      find-semicolon $i++ consistency, bare / fallthrough,
      depth-tracked @import extraction, %NON-COLOR-PROPS font-family
      skip in !apply-color-names
    - Writer fixes: strip-zero-units .0 edge case, null-byte sentinel
      \x[1]→\x0, url-quote non-greedy backtracking, rgba 1.0 fix,
      Level 4 space-sep rgb, UNITS constant (one-time compilation),
      url-quote-value helper extraction
    - Plugin fixes: Dedup added to pipeline (was unused),
      extra-plugins unconditional die guard, at-rule-decl-key case
      sensitivity, custom property !important bypass, .lc cache in
      !same-selectors, dead Values plugin removed
    - Dedup/Merging enhancements: normalize equivalent colors
      (named→hex, bold→700) in declarations-to-key, Unicode .fc in
      selector dedup, rgb→hex in declarations-to-key
    - Refactoring: declarations-to-key shared helper, %DISPATCH
      Callable table, helper extraction (background-layer,
      origin-clip-parts, url-quote-value, normalize-selector split),
      dead return cleanup, README/POD updates
    - Other: CLI level validation, @@name edge case, modern CSS units
      (cap,ic, lh,rlh,vb,vi,cq*), custom property merge (later-wins),
      descendant combinator normalization, comb-cache 500-cap + Lock
      thread safety, UNITS regex one-time compilation
0.0.7  2026-06-04T03:35:20+03:00
    - AST rewrite: replace CSS::Stylesheet with own brace-counting
      parser (Parser.rakumod); own Document tree (Document.rakumod)
      replaces CSS::Writer output; Normalizer.rakumod replaces
      CSS::Properties normalization
    - Zero dependencies: remove CSS::Stylesheet, CSS::Properties,
      CSS::Writer — META6.json depends is now []
    - Drop :$preserve-unknown option and the pre-scan/re-injection
      system it depended on
    - Add !dedup-declarations: remove duplicate property:value pairs
      within a ruleset
    - Add !consolidate-shorthands: consolidate longhands to shorthand
      for margin, padding, border, border-{top,right,bottom,left},
      outline, list-style, background, and font; skip values
      containing var()/calc()/ env()/min()/max()/clamp()
    - Fix !consolidate-shorthands duplicate-declaration bug: use
      %consolidated set to distinguish merger-created shorthands from
      original declarations
    - Fix !consolidate-shorthands ordering: emit shorthand at the
      position of its last longhand (was first), preserving cascade
      order across interleaved non-group properties
    - Fix !consolidate-shorthands mixed !important: skip consolidation
      when longhands disagree on importance (was applying !important
      to all sides if any longhand was important)
    - Fix !write-at-rule space: use $ar.prelude ?? ' ' !! '' instead
      of hardcoded media/font-face check, so @supports, @keyframes,
      @layer, @container also get space before {
    - Fix !extract-braceless quote walkers: use skip-quoted (handles
      backslash escapes) instead of bare while loops
    - Fix Writer quote normalization: skip "→' conversion when value
      already contains single quotes
    - Fix Writer hex color uppercasing: general hex-uppercase step so
      #fff→#FFF (no longer relies on CSS::Properties)
    - Add !important to Dedup !at-rule-decl-key for consistency with
      !ruleset-key
    - Add background and font shorthand consolidation: background
      consolidated for single and multi-layer (comma-separated)
      declarations with optional background-size via position / size
      syntax and optional background-origin/background-clip as box
      values; multi-layer handles cycling per CSS spec when layer
      counts differ across longhands; font consolidated with optional
      font-stretch, requiring only font-size and font-family;
      line-height consumed if present
    - Add %CUSTOM dispatch table in Normalizer for shorthands requiring
      value-level assembly (background, font); simple shorthands
      (margin, padding, border, etc.) stay on the join(' ') path
    - Add !assemble-background and !assemble-font methods to Normalizer;
      !assemble-font normalizes bold→700, normal→400 and skips
      font-weight:400, font-style:normal, font-variant:normal as CSS
      shorthand default values
    - Add has-top-level-comma() and split-top-level() to Util.rakumod
      — comma-at-depth-0 detection and splitting; used for multi-layer
      background dispatch
    - Add $CSS-WIDE-RE guard (initial|inherit|unset|revert) — prevents
      consolidation when any longhand has a CSS-wide keyword value

0.0.6  2026-06-03T21:12:55+03:00
    - Fix re-minification growth (final): colon in media-feature regex
      `/\(.*:.*\)/` was being interpreted as a Raku regex adverb (`:`)
      instead of a literal colon, so the denormalize-prelude fallback
      in !find-at-rule-block was never triggered.  This caused
      !prop-exists to always return False for any @media qualifier
      whose prelude had been space-normalized by !walk-decls, because
      the exact prelude search (with space after `:`) never matched
      the pipeline output (without space after `:`).  Consequence:
      every property inside @media was re-injected on every pass,
      causing unbounded growth.
    - Add normalize-prelude / denormalize-prelude subroutines to
      bridge the two prelude formats
    - Normalize preludes in !walk-decls so qualifier keys are
      consistent
    - Fix !find-at-rule-block to try both normalized and denormalized
      forms during block search
    - Fix !prop-exists to scan ALL at-rule blocks with the same
      prelude (not just the first) for the inner selector
    - Fix !merge-qualified to find the at-rule block that actually
      contains the target inner selector; add idempotency guard to
      skip re-injection when the exact ruleset already exists in the
      block
    - Fix normalize/denormalize-prelude nested-parens handling
      (e.g. calc(), min(), max()) — use a token with &val to match
      balanced inner parens instead of a bare <-[):]>+ capture

0.0.5  2026-06-03T06:24:07+03:00
    - Fix !normalize-block trailing semicolon: always omit (rebuilds
      from scratch), making output consistent with pipeline blocks
    - Fix !find-qual-end depth counting: O(n) single-pass with
      quote/comment skipping instead of O(n²) rescan from 0
    - Fix !normalize-selector quote walking: use skip-quoted for
      backslash-escape handling
    - Fix split-selectors quote walking: use skip-quoted for
      backslash-escape handling (same bug, Util.rakumod)
    - Update Merging.rakumod POD: propertyless rules are skipped
      (not barriers)
    - Update README Level 2: add propertyless-rules note
    - Add test coverage: escaped-quote attribute selector, content
      brace depth guard, @keyframes margin:0px, no-trailing-newline
    - Fix !normalize-all-decls inline quote walkers: use skip-quoted
      (fixes double-backslash \" edge case)
    - Fix split-xpath quote walking: use skip-quoted for consistency
      (Util.rakumod)

0.0.4  2026-06-03T04:48:31+03:00
    - Fix order stability: insert re-injected rulesets at their
      natural @order position (after predecessor) instead of appending
      at end, so first-pass and second-pass selector order is
      identical
    - Add rgb()/rgba() → hex shortening to !minify-value
      (post-processing pass), closing the gap where rgb() values
      inside @keyframes and other pipeline-bypassed blocks stayed
      unshortened
    - Remove stale Merging-plugin !important warning from README —
      !important is part of the Str(:optimize) grouping key, so mixed
      groups never form
    - Fix re-minification growth (remaining): normalize combinator
      spacing (+ / > / ~) in !normalize-selector so selectors like
      .foo+.bar (no space before +) match the pipeline output .foo +
      .bar (with space).  Always call !normalize-selector (not just
      for comma-containing selectors) to catch this case. Prevents
      runaway re-injection on subsequent passes for any selector
      containing tight combinators.

0.0.3  2026-06-03T03:39:52+03:00
    - Fix braceless at-rules (@import, @charset, @namespace) silently
      dropped — extract before parse, prepend to output
    - Fix braceless at-rules trailing newline — suppress separator
      when no ruleset follows
    - Fix @view-transitions and other declaration-only at-rules
      dropped by pipeline
    - Fix @font-face descriptors dropped by !walk-decls
      (e.g. font-display)
    - Fix !merge-props to respect !important cascade
    - Fix :has()/:is()/:where() duplicate rulesets — strip spurious
      truncated prefix ruleset
    - Fix prefers-color-scheme/contrast bogus @media not all — replace
      with original query
    - Fix !replace-bogus-not-all genuine-guard (space before { in
      %by-sel key was missing)
    - Fix !find-at-rule-block and !prop-exists to target correct Nth
      block for duplicate at-rules (e.g. multiple @font-face); fall
      back to block 0 when Nth block was deduped away
    - Fix !find-at-rule-block advance past brace instead of past
      prelude (fragile with space-before-{ variant)
    - Fix !merge-qualified double-semicolon when block body already
      ends with semicolon
    - Fix !prop-exists to scan ALL rulesets for a selector (not just
      the first one), preventing spurious re-injection when
      duplicate-selector rulesets exist at level 1
    - Fix !prop-exists quote normalisation (pre-scanned selectors use
      `"`, pipeline output uses `'`)
    - Fix !prop-exists boundary guard — skip substring matches
      (e.g. `#header` inside `[data-bs-theme='dark'] #header`) while
      allowing valid merged-group matches (e.g. `h1` after `, `)
    - Rewrite 01-basic and 02-plugins with exact golden output (was
      loose regex/count checks)
    - Add 03-edge-cases — 11 subtests covering empty input,
      !important, custom properties, braceless at-rules, attribute
      selectors, nested at-rules, chained merging, @font-face, and more
    - Add 04-golden — 57 exact-output regression tests covering the
      full minification surface including @keyframes, combinators,
      rgb(), @view-transitions, :has()/:is()/:where(),
      prefers-color-scheme, and multi-block @font-face
    - Fix !has-selector-in regex injection — wrap $sel in quotemeta to
      escape metacharacters
    - Fix !merge-qualified inner quote mismatch — normalise `"` → `'`
      for at-rule inner selectors
    - Fix !replace-bogus-not-all inner quote mismatch — same
      normalisation for the media-query path
    - Fix !remove-selector boundary guard — walk backwards past \s
      chars (not just `}`) to handle merged output with :sep("\n")
      separators
    - Fix !walk-decls NUL-counter grep — use explicit block ({ ... })
      instead of broken WhateverCode || to count existing NUL-suffixed
      keys for 3+ duplicate at-rule blocks (e.g. three @font-face)
    - Fix !dedup-selector-lists split on ', ' breaking with attribute
      selectors containing commas — add bracket/paren/string-aware
      split-xpath sub to Util; dump naive split(', ') in Merging; use
      .splice instead of = for AST mutation
    - Fix !merge-decls spurious re-injection after pipeline dedup —
      dedup selector lists in qualifier before prop-exists check via
      new split-selectors sub; re-inject using deduped qualifier
    - Fix upstream xpath crash on pseudo-elements (::before, ::after,
      etc.) — monkey-patch missing `xpath-pseudo-elem` method onto
      CSS::Selector::To::XPath
    - Fix idempotency — strip CSS comments before declaration
      extraction in !walk-decls so re-minified output doesn't grow
    - Fix non-adjacent same-declaration merging — propertyless rules
      are now treated as barriers (last), preserving cascade order
    - Fix propertyless rules break non-adjacent merging — change from
      `last` to `$i++; next` so parser-artifact propertyless rulesets
      are skipped instead of acting as hard barriers
    - Fix cascade-order change during same-declaration merging — track
      last-merged position for each group and sort by it before
      assigning to $stylesheet.rules, preserving which rule wins by
      cascade order
    - Fix @keyframes keyframe selector order — add @order array
      alongside %by-sel in !walk-decls, iterate @order in !merge-decls
      Phase 2 instead of %by-sel.kv (Raku Hash iteration is
      randomized)
    - Remove dead `@skipped` / `@merged.append` code from Merging
      plugin
    - Update documentation: README and POD limitations reflect fixed
      bugs
    - Fix !walk-decls regex fragility — replace <-[:;]>+ / <-[;}]>+
      regex with parse-body character-walker that tracks strings,
      paren depth, and comments, correctly handling ; and } inside
      values
    - Fix /*! */ license extraction inside string values — replace
      naive regex with !extract-licenses character-walking state
      machine
    - Normalize declaration values in !merge-decls re-injection path —
      add !minify-value method that shortens colors, removes
      zero-units, normalizes font-weight, and normalizes quotes (" →
      ') for re-injected declarations
    - Add !normalize-all-decls post-processing pass — walks the output
      string and normalizes values + deduplicates declarations in ALL
      {…} blocks, catching @keyframes keyframes, nested at-rules, and
      other blocks the pipeline can't reach (recursive leaf-block
      walker)
    - Fix broken hex shortening regex in !minify-value (was using
      self- referential captures that never matched; now properly
      backrefs each hex digit pair) so 6-char colors are shortened in
      @keyframes keyframe blocks and other post-processed blocks
    - Fix zero-unit regex in !minify-value — added `<!after \d>`
      negative lookbehind to prevent stripping units from non-zero
      values containing a `0` digit (e.g. `30px` → `30`); now only
      standalone `0px` → `0`
    - Fix golden test 70 for @keyframes normalization — accept both
      keyframe selector orders (from/to vs to/from) since hash
      iteration order in %by-sel is non-deterministic between Raku
      runs
    - Fix parse-body double-increment when : or ; found inside parens
      (skipped one character after :/; inside functions — never fires
      in practice since : in property names is invalid CSS)
    - Fix has-brace check in !walk-decls — use quote-aware walker
      instead of naive / '{' / regex that would false-positive on {
      inside quoted strings (theoretical, extremely rare)
    - Fix braceless at-rule extraction — replace .*?; regex with
      quote/paren-aware find-semicolon walker so ; inside url() or
      strings is correctly skipped (theoretical, ; inside URL is rare)
    - Fix find-semicolon double-increment on (/) — the when blocks
      were incrementing $i inside AND falling through to $i++ at the
      while-loop bottom, skipping one character after each paren
    - Remove dead code (= Empty) in Merging Plugin line 98 — $next is
      about to be dropped, the assignment had no effect
    - Fix re-minification growth — normalize selectors (commas → `, `)
      in !walk-decls and media-query preludes (colons → `: `) in
      !find-at-rule-block so extracted qualifiers match pipeline
      output format, preventing runaway re-injection on subsequent
      passes

0.0.2  2026-05-21T16:27:43+03:00
    - Fix `1em` → `em` regression from CSS::Writer (override write-num
      for `em`/`ex` in Writer.rakumod)
    - Remove %KNOWN-PROPS filter in !walk-decls so all known
      properties are pre-scanned (catches CSS3 values like `position:
      sticky`, `overflow: clip` that the grammar drops)
    - Fix falsy-zero bug in !prop-exists (Raku `0` is falsy, causing
      all rulesets at position 0 to always re-inject)
    - Add %SHORTHAND map in !prop-exists to avoid re-injecting
      longhands consolidated to shorthands by CSS::Properties
      (e.g. `background-color` → `background`)
    - Fix same falsy-zero bug in at-rule path of !prop-exists

0.0.1  2026-05-21T01:30:31+03:00
    - Initial version
