Skip to content

Commit 97f4cb6

Browse files
authored
Merge pull request #8 from imagewize/pattern-normalizer
Pattern Normalizer
2 parents 711b8ec + 870317d commit 97f4cb6

16 files changed

Lines changed: 2913 additions & 3 deletions

.vibe/config.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,6 @@ denylist = [
120120
max_output_bytes = 16000
121121
default_timeout = 30
122122
denylist_standalone = [
123-
"python",
124-
"python3",
125123
"ipython",
126124
"bash",
127125
"sh",

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,45 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [2.3.0] - 2026-05-14
8+
9+
### Added
10+
11+
- **`pattern:diff` command** (`src/Commands/PatternDiffCommand.php`) — compares a Gutenberg clipboard paste against an existing PHP pattern file and reports differences (missing translations, editor-only attributes, CSS drift, WooCommerce block issues). Options:
12+
- `--from-stdin` — read Gutenberg HTML from stdin (pipe from `pbpaste`)
13+
- `--apply` — merge clipboard changes into the PHP file, preserving all PHP translation wrappers (`esc_html_e`, `esc_attr_e`, `wp_kses_post`, `get_template_directory_uri`)
14+
- `--dry-run` — with `--apply`: print the merged result to stdout instead of writing to disk
15+
- `--theme` — theme config to use (default: `elayne`)
16+
- `--json` — output diff results as JSON
17+
- `--similarity-threshold` — minimum block similarity score to consider a match (0–1, default: `0.95`)
18+
- `--show-suggestions` / `-s` — include fix suggestions in the output
19+
20+
- **`BlockNormalizer` class** (`src/PatternDiff/BlockNormalizer.php`) — normalises block HTML for comparison: strips editor-only attributes (`__privatePreviewState`, `isNotStackedOnMobile`, constrained layout flags, product-collection block flags), normalises CSS value tokens (font-size slugs → CSS variables, spacing presets), collapses whitespace, and decodes HTML entities. Exposes `detectEditorAttributes()`, `extractOuterBlockType()`, and `hasWooCommerceBlocks()` as public helpers.
21+
22+
- **`PatternDiffer` class** (`src/PatternDiff/PatternDiffer.php`) — calculates structural similarity between clipboard HTML and a PHP pattern file. Extracts block HTML from PHP, computes a similarity score via `similar_text`, and finds CSS, structural, and text-node differences. WooCommerce plugin paths (`wp-content/plugins/woocommerce/patterns/*`) are exempt via `isExempt()`.
23+
24+
- **`PatternSyncer` class** (`src/PatternDiff/PatternSyncer.php`) — implements the `--apply` merge strategy: extracts the PHP docblock header, builds a `text → PHP call` map from all translation wrappers in the original file, applies structural fixes to the clipboard (strips `__privatePreviewState`, normalises font-size slugs, removes nested `<p>` copy artefacts), then walks every HTML text node replacing raw strings with their original wrappers or generating new `esc_html_e()` calls for new strings.
25+
26+
- **`TranslationDetector` class** (`src/PatternDiff/TranslationDetector.php`) — finds text nodes in clipboard HTML that are not yet wrapped in a translation function in the existing PHP file. Generates `esc_html_e()` fix suggestions. Exposes `isTranslated()`, `generateTranslationWrapper()`, and `extractTranslatableText()` as public helpers.
27+
28+
- **`WooCommerceValidator` class** (`src/PatternDiff/WooCommerceValidator.php`) — validates WooCommerce-specific block structure in the diff context: checks `isDescendentOfQueryLoop` on WC blocks inside `product-template`, validates `<div>` wrappers and layout attributes, and inspects `product-collection` query metadata.
29+
30+
- **PHPUnit test suite for `PatternDiff`** — 6 new test files covering all public methods in the feature:
31+
- `tests/PatternDiff/BlockNormalizerTest.php` — 29 tests for all normalisation methods
32+
- `tests/PatternDiff/PatternDifferTest.php` — 5 tests (diff result shape, identical-file similarity, PHP extraction, exempt paths)
33+
- `tests/PatternDiff/PatternSyncerTest.php` — 22 tests (header extraction/stripping, PHP call map, structural fixes, translation restore, wrapper generation)
34+
- `tests/PatternDiff/TranslationDetectorTest.php` — 16 tests (missing translation detection, `isTranslated`, `generateTranslationWrapper`, `extractTranslatableText`)
35+
- `tests/PatternDiff/WooCommerceValidatorTest.php` — 6 tests (validate, extractWooCommerceBlocks, isDescendantOfProductTemplate)
36+
- `tests/Commands/PatternDiffCommandTest.php` — 9 tests (command name, path validation, apply/dry-run guards, JSON output, threshold, theme option)
37+
38+
### Fixed
39+
40+
- **`PatternDiffer::isTranslatable()`** — the `if (preg_match(...))` guard around the CSS-value `return false` branch was accidentally omitted, causing the method to always return `false` after the single-character check. All text nodes after that point were silently skipped by the translation detector.
41+
42+
### Changed
43+
44+
- **`.vibe/config.toml`**`python` and `python3` removed from the vibe tools denylist to allow local script execution during pattern diffing.
45+
746
## [2.2.3] - 2026-05-13
847

948
### Added

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,42 @@ pt-cli check:templates /path/to/templates/archive-product.html --theme=elayne
7171
pt-cli check:templates /path/to/templates/ --theme=elayne --autofix
7272
```
7373

74+
### Pattern Diff & Sync
75+
76+
Compare a Gutenberg clipboard paste against an existing PHP pattern file, or apply
77+
the changes back while preserving all PHP translation wrappers.
78+
79+
```bash
80+
# Report differences only (shows missing translations, editor attrs, CSS issues)
81+
pbpaste | pt-cli pattern:diff patterns/woo-signature-pieces.php --from-stdin
82+
83+
# Include fix suggestions in the diff report
84+
pbpaste | pt-cli pattern:diff patterns/woo-signature-pieces.php --from-stdin --show-suggestions
85+
86+
# Output diff results as JSON (for tooling integration)
87+
pbpaste | pt-cli pattern:diff patterns/woo-signature-pieces.php --from-stdin --json
88+
89+
# Preview the merged result without touching the file
90+
pbpaste | pt-cli pattern:diff patterns/woo-signature-pieces.php --from-stdin --apply --dry-run
91+
92+
# Apply clipboard changes to the PHP file (preserves esc_html_e, esc_attr_e, etc.)
93+
pbpaste | pt-cli pattern:diff patterns/woo-signature-pieces.php --from-stdin --apply
94+
95+
# Lower the similarity threshold for loosely matched blocks (default: 0.95)
96+
pbpaste | pt-cli pattern:diff patterns/woo-signature-pieces.php --from-stdin --similarity-threshold=0.80
97+
```
98+
99+
**What `--apply` does:**
100+
101+
1. Strips editor-only attributes (`__privatePreviewState`) from block JSON.
102+
2. Fixes bare font-size slug values (`font-size:small`) → CSS variable (`font-size:var(--wp--preset--font-size--small)`).
103+
3. Removes nested `<p>` copy artefacts introduced by the Gutenberg clipboard.
104+
4. Re-maps every text node back to its original PHP wrapper (`esc_html_e`, `esc_attr_e`, `wp_kses_post`) from the existing file.
105+
5. Generates a new `esc_html_e()` wrapper for any text that is new in the clipboard.
106+
6. Preserves the PHP docblock header unchanged.
107+
108+
The file is only written when `--apply` is used **without** `--dry-run`.
109+
74110
## Commands Reference
75111

76112
| Command | Description |
@@ -81,6 +117,7 @@ pt-cli check:templates /path/to/templates/ --theme=elayne --autofix
81117
| `style:create` | Scaffold a WordPress theme style variation JSON |
82118
| `check` | Check PHP pattern files for compliance violations |
83119
| `check:templates` | Check HTML template/part files for block validation drift |
120+
| `pattern:diff` | Diff Gutenberg clipboard against a pattern file, or apply changes preserving PHP |
84121

85122
## Workflow
86123

@@ -96,6 +133,19 @@ pt-cli check:templates /path/to/templates/ --theme=elayne --autofix
96133
| 4 | `pt-cli pattern:create --shell-only` | Create PHP file with paste marker | Host |
97134
| 5 | Replace marker | Paste blocks into pattern file | Host |
98135

136+
### Pattern Sync Workflow (updating existing patterns)
137+
138+
When an existing PHP pattern needs updating from the Site Editor, use `pattern:diff --apply`
139+
instead of pasting manually — it keeps all `esc_html_e()` and other PHP wrappers intact.
140+
141+
| Step | Command | Purpose |
142+
|------|---------|---------|
143+
| 1 | Edit pattern in Site Editor | Make structural/layout changes |
144+
| 2 | Copy all blocks (Cmd+A, Cmd+C) | Copy updated block HTML to clipboard |
145+
| 3 | `pbpaste \| pt-cli pattern:diff <file> --from-stdin --apply --dry-run` | Preview merged result |
146+
| 4 | `pbpaste \| pt-cli pattern:diff <file> --from-stdin --apply` | Write merged result to file |
147+
| 5 | `pt-cli check <file> --theme=elayne` | Verify compliance passes |
148+
99149
### Compliance Workflow (three-pass)
100150

101151
| Pass | Tool | Purpose | Where |

bin/pt-cli

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@ foreach ($autoloadPaths as $path) {
1414
}
1515

1616
use Imagewize\PtCli\Commands\CheckCommand;
17+
use Imagewize\PtCli\Commands\PatternDiffCommand;
1718
use Imagewize\PtCli\Commands\Scaffold\LayoutCreateCommand;
1819
use Imagewize\PtCli\Commands\Scaffold\PatternCreateCommand;
1920
use Imagewize\PtCli\Commands\Scaffold\PatternListCommand;
2021
use Imagewize\PtCli\Commands\Scaffold\StyleCreateCommand;
2122
use Imagewize\PtCli\Commands\TemplateCheckCommand;
2223
use Symfony\Component\Console\Application;
2324

24-
$application = new Application('pt-cli', '2.2.3');
25+
$application = new Application('pt-cli', '2.3.0');
2526
$application->add(new CheckCommand());
2627
$application->add(new TemplateCheckCommand());
2728
$application->add(new PatternCreateCommand());
2829
$application->add(new LayoutCreateCommand());
2930
$application->add(new StyleCreateCommand());
3031
$application->add(new PatternListCommand());
32+
$application->add(new PatternDiffCommand());
3133
$application->setDefaultCommand('list');
3234
$application->run();

0 commit comments

Comments
 (0)