diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md
index 1644a22..da038db 100644
--- a/.paul/PROJECT.md
+++ b/.paul/PROJECT.md
@@ -14,7 +14,7 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online -
|-----------|-------|
| Version | 0.333 |
| Status | Production |
-| Last Updated | 2026-04-20 |
+| Last Updated | 2026-04-30 |
## Requirements
@@ -32,6 +32,7 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online -
- [x] Domain-Driven Architecture (migracja z legacy zakończona)
- [x] Szybka edycja custom_label_0..4 na liscie produktow admina (toggle sesyjny + autocomplete)
- [x] Poprawna kalkulacja kosztu transportu na /koszyk-podsumowanie (fix delivery_free bez uwzglednienia progu)
+- [x] Linki produktów z permutacją w feedzie Google działają (separator `_` w URL, konwersja `_`→`|` w warstwie front, regex `[0-9_-]+` w pp_routes)
### Active (In Progress)
@@ -83,12 +84,13 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online -
| `id` w tabbed FormEdit przez `hiddenFields` | Zapobiega insert zamiast update przy edycji encji | 2026-04-18 | Active |
| Inline custom labels w product list przez sesyjny toggle | Szybszy workflow dla Google XML bez wejscia w edycje produktu | 2026-04-19 | Active |
| Kalkulacja kosztu transportu na /koszyk-podsumowanie w kontrolerze (nie w szablonie) | Spojnosc logiki progu darmowej dostawy miedzy /koszyk i /koszyk-podsumowanie | 2026-04-20 | Active |
+| Separator URL permutacji `_` zamiast `/` (DB pozostaje `|`) | Jeden segment URL dopasowywalny przez pp_routes; konwersja `_`→`|` w warstwie front | 2026-04-30 | Active |
## Success Metrics
| Metric | Target | Current | Status |
|--------|--------|---------|--------|
-| Testy | >800 | 834 | On track |
+| Testy | >800 | 841 | On track |
| Pokrycie architektury DDD | 100% | 100% | Achieved |
## Tech Stack
@@ -117,4 +119,4 @@ Quick Reference:
---
*PROJECT.md - Updated when requirements or context change*
-*Last updated: 2026-04-20 after Phase 17*
+*Last updated: 2026-04-30 after Phase 18*
diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index e40d850..449472b 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -38,6 +38,7 @@ Status: Planning
| 9 | Apilo email notification + infinite retry | 1 | Done | 2026-03-19 |
| 15 | Scontainers edit saves as new record | 1 | Done | 2026-04-18 |
| 17 | Cart summary transport cost fix | 1 | Done | 2026-04-20 |
+| 18 | Google feed permutation URL fix | 1 | Done | 2026-04-30 |
## Feature
@@ -125,5 +126,11 @@ Status: Planning
**Scope:** Przekazac z `ShopBasketController::summaryView()` do szablonu wyliczony `transport_cost_effective` i flage `free_delivery_applies` uwzgledniajaca prog. Zaktualizowac summary-view.php aby uzywal tych kluczy zamiast surowej flagi `delivery_free`. Test jednostkowy dla logiki wyliczenia.
+### Phase 18 — Google feed permutation URL fix
+
+**Problem:** URL produktu z permutacją atrybutów w feedzie Google miał format `/slug/20-170/21-175` (slash między parami). Wzorzec routingu `pp_routes` używa `[0-9-]+`, który nie obejmuje `/`, więc URL nie matchuje żadnej trasy i `index.php` ładuje stronę główną. Klienci z GMC trafiają na home zamiast na produkt z wybraną kombinacją.
+
+**Scope:** Zmienić separator z `/` na `_` w generatorze feedu (`ProductRepository::appendCombinationToXml`), rozszerzyć regex routingu o `_` (`Helpers`), dodać konwersję `_` → `|` w warstwie front (`LayoutEngine`), preselekcja wartości atrybutu w partialu na podstawie `permutation_hash` z URL. Plus unit testy regex + generator linku.
+
---
-*Last updated: 2026-04-20 (Phase 17 complete)*
+*Last updated: 2026-04-30 (Phase 18 complete)*
diff --git a/.paul/STATE.md b/.paul/STATE.md
index affd51e..c80b6ce 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -2,22 +2,23 @@
## Project Reference
-See: .paul/PROJECT.md (updated 2026-04-18)
+See: .paul/PROJECT.md (updated 2026-04-30)
**Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform.
-**Current focus:** Phase 17 complete - loop closed
+**Current focus:** Phase 18 complete - loop closed
## Current Position
Milestone: Hotfix
-Phase: 17 of 17 (Cart summary transport cost fix) - Complete
-Plan: 17-01 complete
-Status: UNIFY complete, ready for next PLAN loop (transition-phase pending)
-Last activity: 2026-04-20 - Closed loop for .paul/phases/17-cart-summary-transport-cost-fix/17-01-PLAN.md
+Milestone: Hotfix
+Phase: 18 of 18 (Google feed permutation URL fix) - Complete
+Plan: 18-01 complete
+Status: UNIFY complete, ready for next PLAN loop (transition-phase git commit pending)
+Last activity: 2026-04-30 - Closed loop for .paul/phases/18-google-feed-permutation-url-fix/18-01-PLAN.md
Progress:
-- Milestone: [##########] 100%
-- Phase 17: [##########] 100%
+- Milestone: [##########] 100% (Hotfix rolling)
+- Phase 18: [##########] 100%
## Loop Position
@@ -43,10 +44,17 @@ Phase 14: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-16]
Phase 15: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-18]
Phase 16: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-19]
Phase 17: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-20]
+Phase 18: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-30]
```
## Accumulated Context
### Decisions
+- 2026-04-30: Phase 18 loop closed with SUMMARY at .paul/phases/18-google-feed-permutation-url-fix/18-01-SUMMARY.md
+- 2026-04-30: Transition-phase git commit for Phase 18 not executed in this UNIFY run (deferred — pattern z faz 15/16/17)
+- 2026-04-30: Phase 18 APPLY complete — 4 pliki silnika + 2 nowe pliki testów (HelpersRoutingTest 4 testy, ProductFeedLinkTest 3 testy); suita 841 zielona
+- 2026-04-30: Created Phase 18 plan at .paul/phases/18-google-feed-permutation-url-fix/18-01-PLAN.md
+- 2026-04-30: Phase 18 — separator URL permutacji `/` → `_`; konwersja `_` → `|` w warstwie front; regex `[0-9_-]+` w pp_routes
+- 2026-04-30: Phase 18 — override /feature-dev (hotfix z konkretną instrukcją), brak redirectów 301, brak automatycznych akcji post-deploy
- 2026-04-20: Phase 17 loop closed with SUMMARY at .paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md
- 2026-04-20: Transition-phase git commit for Phase 17 not executed in this UNIFY run (deferred)
- 2026-04-20: Phase 17 APPLY complete - human-verify checkpoint approved, 834 testow zielonych (6 nowych)
@@ -89,17 +97,17 @@ None.
### Blockers/Concerns
None.
-### Skill Audit (Phase 16)
+### Skill Audit (Phase 18)
| Expected | Invoked | Notes |
|----------|---------|-------|
-| /feature-dev | ○ | User-approved override during APPLY |
-| /koniec-pracy | ○ | Marked as available by user; execute on session close workflow |
+| /feature-dev | ○ | User-approved override (hotfix z konkretną instrukcją) |
+| /koniec-pracy | ○ | Pending — uruchomić przy zakończeniu sesji jeśli release wchodzi do update package |
## Session Continuity
-Last session: 2026-04-20
-Stopped at: Phase 17 complete, loop closed
-Next action: Start next milestone or create next phase plan (transition-phase commit pending)
-Resume file: .paul/phases/17-cart-summary-transport-cost-fix/17-01-SUMMARY.md
+Last session: 2026-04-30
+Stopped at: Phase 18 complete, loop closed
+Next action: Start next phase plan (transition-phase git commit pending), lub uruchomić /koniec-pracy jeśli zamykamy sesję
+Resume file: .paul/phases/18-google-feed-permutation-url-fix/18-01-SUMMARY.md
---
*STATE.md — Updated after every significant action*
diff --git a/.paul/changelog/2026-04-30.md b/.paul/changelog/2026-04-30.md
new file mode 100644
index 0000000..aec87eb
--- /dev/null
+++ b/.paul/changelog/2026-04-30.md
@@ -0,0 +1,25 @@
+# 2026-04-30
+
+## Co zrobiono
+
+- [Phase 18, Plan 01] Fix linków produktów z permutacją atrybutów w feedzie Google
+- Separator URL między parami `attr-val` zmieniony z `/` na `_` w `ProductRepository::appendCombinationToXml`
+- Wzorzec routingu `pp_routes` rozszerzony o `_` (`[0-9-]+` → `[0-9_-]+`) w `Helpers::htacces`
+- Konwersja `_` → `|` w `LayoutEngine` przed wywołaniem `ProductRepository::findCached`
+- Preselekcja wartości atrybutu na podstawie `permutation_hash` z URL w partialu `product-attribute.php`
+- 2 nowe pliki testów: `HelpersRoutingTest` (4 testy) + `ProductFeedLinkTest` (3 testy via Reflection)
+- Suita PHPUnit: 834 → 841 zielonych
+
+## Zmienione pliki
+
+- `autoload/Domain/Product/ProductRepository.php`
+- `autoload/Shared/Helpers/Helpers.php`
+- `autoload/front/LayoutEngine.php`
+- `templates/shop-product/_partial/product-attribute.php`
+- `tests/Unit/Shared/Helpers/HelpersRoutingTest.php`
+- `tests/Unit/Domain/Product/ProductFeedLinkTest.php`
+- `.paul/STATE.md`
+- `.paul/PROJECT.md`
+- `.paul/ROADMAP.md`
+- `.paul/phases/18-google-feed-permutation-url-fix/18-01-PLAN.md`
+- `.paul/phases/18-google-feed-permutation-url-fix/18-01-SUMMARY.md`
diff --git a/.paul/codebase/testing.md b/.paul/codebase/testing.md
index 7a67a5b..32346c2 100644
--- a/.paul/codebase/testing.md
+++ b/.paul/codebase/testing.md
@@ -4,8 +4,8 @@
| Metric | Value |
|--------|-------|
-| Total tests | **828** |
-| Total assertions | **2306** |
+| Total tests | **841** |
+| Total assertions | **2330** |
| Framework | PHPUnit 9.6 (`phpunit.phar`) |
| Bootstrap | `tests/bootstrap.php` |
| Config | `phpunit.xml` |
diff --git a/.paul/docs/TECH_CHANGELOG.md b/.paul/docs/TECH_CHANGELOG.md
index 58bf49c..ce12c11 100644
--- a/.paul/docs/TECH_CHANGELOG.md
+++ b/.paul/docs/TECH_CHANGELOG.md
@@ -2,6 +2,16 @@
> Chronologiczny log zmian technicznych — co i dlaczego.
+## v0.350 (2026-04-30)
+
+- Naprawiono linki produktow z permutacja atrybutow w feedzie Google: separator par `attr-val` w URL zmieniony z `/` na `_`. Stary format `/slug/20-170/21-175` nie matchowal sie w `pp_routes` (regex `[0-9-]+` nie obejmuje `/`), wiec klienci z GMC ladowali na strone glowna zamiast na produkt.
+- `ProductRepository::appendCombinationToXml`: `str_replace('|', '/', ...)` -> `str_replace('|', '_', ...)` w obu galeziach (z `seo_link` i fallback `p-id-name`).
+- `Helpers::htacces`: regex routingu produktow z permutacja rozszerzony do `/([0-9_-]+)$` w obu wariantach.
+- `LayoutEngine.php` (// PRODUKT): konwersja `_` -> `|` przed wywolaniem `ProductRepository::findCached` — format DB pozostaje bez zmian (`attr-val|attr-val`).
+- `templates/shop-product/_partial/product-attribute.php`: preselekcja wartosci atrybutu na podstawie `permutation_hash` z URL (`$forced_value_id`); wartosc `is_default` uzywana tylko gdy URL nie wymusza wyboru. Dotyczy `checked` na inpucie i emisji bloku `fradio_label_click(...)`.
+- Dodano 7 testow jednostkowych: `HelpersRoutingTest` (4 testy regex + assercje na zawartosci pliku) i `ProductFeedLinkTest` (3 testy `appendCombinationToXml` via `ReflectionMethod` z mockiem `TransportRepository`). Suita: 841 testow / 2330 assertions.
+- Wymagane akcje na produkcji po deployu: regeneracja `pp_routes` (`Helpers::htacces()`), wyczyszczenie klucza `pp_routes:all` w Redis, regeneracja `google-feed.xml`, resubmit feedu w GMC.
+
## v0.349 (2026-04-20)
- Naprawiono wyswietlanie kosztu transportu na /koszyk-podsumowanie: transporty z `delivery_free=1` pokazuja teraz rzeczywisty koszt ponizej progu `settings.free_delivery`, a 0,00 zl dopiero po osiagnieciu progu (spojnie z lista na /koszyk).
diff --git a/.paul/docs/TODO.md b/.paul/docs/TODO.md
index e9bb9d3..5cda327 100644
--- a/.paul/docs/TODO.md
+++ b/.paul/docs/TODO.md
@@ -3688,3 +3688,6 @@ Dodać możliwość ustawienia limitu znaków w wiadomościach do produktu
## SonarQube - v0.349 - brak nowych issues
+
+
+## SonarQube - v0.350 - brak nowych issues
diff --git a/.paul/phases/18-google-feed-permutation-url-fix/18-01-PLAN.md b/.paul/phases/18-google-feed-permutation-url-fix/18-01-PLAN.md
new file mode 100644
index 0000000..7a59d91
--- /dev/null
+++ b/.paul/phases/18-google-feed-permutation-url-fix/18-01-PLAN.md
@@ -0,0 +1,258 @@
+---
+phase: 18-google-feed-permutation-url-fix
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - autoload/Domain/Product/ProductRepository.php
+ - autoload/Shared/Helpers/Helpers.php
+ - autoload/front/LayoutEngine.php
+ - templates/shop-product/_partial/product-attribute.php
+ - tests/Unit/Domain/Product/ProductRepositoryTest.php
+ - tests/Unit/Shared/Helpers/HelpersTest.php
+autonomous: true
+delegation: off
+---
+
+
+## Goal
+Naprawić linki produktów z permutacją atrybutów w feedzie Google: zamienić separator `/` na `_` między parami `attr-val`, dopasować routing `pp_routes`, konwersję `_` → `|` w warstwie front oraz preselekcję wartości atrybutów na podstawie `permutation_hash` z URL.
+
+## Purpose
+URL z formatu `/slug/20-170/21-175` nie matchował się w `pp_routes` (wzorzec `[0-9-]+` nie obejmuje `/`), więc Google Merchant Center prowadził klientów na stronę główną zamiast na produkt z wybraną kombinacją atrybutów. Strata ruchu komercyjnego z feedu.
+
+## Output
+- 4 pliki silnika z nowym separatorem `_`
+- Unit testy: regex routingu (Helpers) + generator linku (ProductRepository::appendCombinationToXml)
+- SUMMARY z listą akcji post-deploy do wykonania ręcznie na produkcji
+
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+@.paul/codebase/architecture.md
+
+## Source Files
+@autoload/Domain/Product/ProductRepository.php
+@autoload/Shared/Helpers/Helpers.php
+@autoload/front/LayoutEngine.php
+@templates/shop-product/_partial/product-attribute.php
+
+
+- **Testy** — Czy dodać unit testy dla zmian?
+ → Odpowiedź: Tak — pełne pokrycie (Helpers regex + ProductRepository::appendCombinationToXml)
+- **Post-deploy** — Czy wykonać regenerację routes/cache/feedu w ramach fazy?
+ → Odpowiedź: Nic — tylko kod; akcje produkcyjne udokumentowane w SUMMARY
+- **Redirect 301** — Czy dodać redirecty ze starych URL-i?
+ → Odpowiedź: Nie — Google sam zaktualizuje linki z feedu
+- **Skills** — /feature-dev required w SPECIAL-FLOWS?
+ → Odpowiedź: Override — pomiń (hotfix z konkretną instrukcją, jak w fazach 15/16/17)
+
+
+
+
+
+## AC-1: Generator linku w feedzie używa `_`
+```gherkin
+Given produkt z permutacją atrybutów (permutation_hash = "20-170|21-175")
+When wywołany jest ProductRepository::appendCombinationToXml dla feedu Google
+Then wygenerowany URL zawiera segment `20-170_21-175` (jeden segment, separator `_`)
+And nie zawiera `/` między parami atrybutów
+And dotyczy obu gałęzi (z seo_link i fallback p-id-name)
+```
+
+## AC-2: Routing `pp_routes` matchuje URL z `_`
+```gherkin
+Given wzorzec routingu wygenerowany przez Helpers dla produktu z permutacją
+When URI to `slug-produktu/20-170_21-175`
+Then regex `[0-9_-]+` dopasowuje cały segment permutacji
+And `permutation_hash` w wynikowych GET to `20-170_21-175`
+And dotyczy obu wariantów (z seo_link i fallback p-id-name)
+```
+
+## AC-3: Front konwertuje `_` z URL na `|` przed zapytaniem do bazy
+```gherkin
+Given GET['permutation_hash'] = "20-170_21-175"
+When LayoutEngine renderuje blok PRODUKT
+Then ProductRepository::findCached otrzymuje argument "20-170|21-175"
+And gdy GET['permutation_hash'] nie istnieje, findCached otrzymuje null
+```
+
+## AC-4: Partial atrybutu preselectuje wartość z URL
+```gherkin
+Given URL produktu z permutation_hash zawierającym parę dla bieżącego atrybutu
+When renderuje się templates/shop-product/_partial/product-attribute.php
+Then aktywna (checked) jest wartość z URL, nie z is_default
+And gdy atrybut nie występuje w hashu, zachowane jest stare zachowanie (is_default)
+And blok