feat(new-layout): add-to-cart handler + piece configurator (Phase 02 plans 01-02)

Plan 02-01 (piece/crop configurator, complete):
- #piece reuse z shared partial product-cover-thumbnails.tpl
- 8 hidden inputs (is_crop, crop_pos_x/y, crop_width/height, piece_bg_top/left, is_reflection) w formie #add-to-cart-or-refresh
- Defensive setup w custom.js: setTimeout(600) init, no-op override totalpriceinfospecific/prod, DOM stubs
- CSS scope pod body#product .product-size-data .product-size-data--new

Plan 02-02 (add-to-cart submission, PARTIAL):
- Capture-phase native addEventListener (useCapture=true) blokuje PS core crash
  (button poza formą w nowym layoucie — closest('form') zwracało 0)
- Manualny AJAX POST: form.serialize() + qty + add=1&action=update do /pl/koszyk
- Fancybox-blocker port z custom.js:327 (nie odpalał się bo selector 0 matches)
- Manual sync is_crop/crop_width/height przed POST (obejście crash checkedHandler)
- prestashop.emit('updatedCart') + defensive blockcart refresh fetch
- Loading spinner + success flash CSS
- Inline handler mirror w product.tpl z idempotency guard (window.__p02p02Bound)
  — cache-buster dla browser cachowanego custom.js

Deferred do Plan 02-03 (customization + modal blocker dla production):
- Customization nie zapisuje się (squaremeter hook gate'owany discretion=on + brak dimension fields)
- Success modal (wymaga POST do /module/ps_shoppingcart/ajax)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 23:33:45 +02:00
parent 161c090ef0
commit 7ac795ba3f
24 changed files with 5447 additions and 2569 deletions

View File

@@ -0,0 +1,331 @@
---
phase: 01-product-variants-fix
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- themes/ayon/templates/catalog/product.tpl
- themes/ayon/assets/css/custom.scss
- themes/ayon/assets/css/custom.css
- themes/ayon/assets/js/custom.js
autonomous: false
delegation: off
---
<objective>
## Goal
W nowym layoucie strony produktu (gałąź `{if $smarty.server.REMOTE_ADDR == '89.69.31.86'}`) doprowadzić blok `.product-variants` w sekcji `.product-box.product-variants-data` (pod nagłówkiem „Wybierz wersję kolorystyczną") do stanu:
1. **Wizualnie** — wygląda jak wzór `d:\temp\Frame 33.png`:
- kafelki wariantów (miniaturki) ułożone obok siebie w rzędzie (min. 3 w linii na desktopie, responsywnie),
- każdy kafelek = kwadratowe zdjęcie tekstury wariantu,
- pod każdym kafelkiem podpis z nazwą wariantu (np. „Lorem ipsum" w makiecie → `{$group_attribute.name}`),
- aktywny wariant wyróżniony (obramowanie lub inny stan aktywny),
- bez dziedziczenia stylów starego „box-color-variants" (które zakładały modal/overlay).
2. **Funkcjonalnie** — kliknięcie miniaturki wariantu zmienia wariant produktu (przekierowanie/refresh PrestaShop do właściwej kombinacji), analogicznie do starego wyglądu. Pozostałe funkcje strony produktu (konfigurator rozmiaru, odbicie, dodanie do koszyka) NIE mogą zostać zepsute.
## Purpose
Klient używa IP `89.69.31.86` do prezentacji nowego wyglądu. Wariant kolorystyczny jest kluczowym elementem konwersji — bez działającej zmiany wariantu strona produktu jest nieużywalna w nowym layoucie.
## Output
- Zmieniony `product.tpl` (tylko sekcja `{if ... == '89.69.31.86'}` — część dotycząca bloku `product_variants`).
- Nowe reguły SCSS/CSS dla `.product-variants-data .product-variants` widoczne TYLKO w nowym layoucie (np. scope `body#product .product-box.product-variants-data .product-variants ...`).
- Ewentualnie: drobna korekta w `custom.js` zapewniająca zmianę wariantu po kliknięciu (delegowany handler zamiast bezpośredniego `.click()`), bez ingerowania w starą ścieżkę.
- SUMMARY.md w `.paul/phases/01-product-variants-fix/01-01-SUMMARY.md`.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@themes/ayon/templates/catalog/product.tpl
@themes/ayon/templates/catalog/_partials/product-variants.tpl
@themes/ayon/assets/css/custom.scss
@themes/ayon/assets/js/custom.js
## Reference Mockup
- `d:\temp\Frame 33.png` — 3 kwadratowe kafelki obok siebie, pod każdym krótki podpis tekstowy.
## Current State (z researchu)
- Partial `product-variants.tpl`:
- Dla `$product_variant_mode == 2` (color) generuje `<ul id="group_{id}"><li class="wariant_kolorystyczny"><label><input type="radio" name="group[..]"><img .../><span class="color ..."></span></label></li>...</ul>`.
- Radio + label to standardowy mechanizm PrestaShop do zmiany wariantu (core.js nasłuchuje `change` na `input[name^="group["]`).
- W nowym layoucie (linia ~604607 w `product.tpl`) partial jest włączany z `$product_variant_mode = 2` w bloku:
```
<div class="product-box product-variants-data">
<h4 class="block-title">Wybierz wersję kolorystyczną</h4>
{include file='catalog/_partials/product-variants.tpl'}
</div>
```
ale NIE jest owinięty w `#box-color-variants`, przez co istniejące reguły CSS i handlery JS pisane pod stary layout (modal z fadeIn/fadeOut) nie aplikują się.
- `custom.js:225` — `jQuery(".wariant_kolorystyczny").click(...)` — bezpośrednie binding, odpala tylko logikę „piece" konfiguratora (nie zmiana wariantu). Zmiana wariantu powinna wychodzić z natywnego PrestaShop flow (change na radio).
- `custom.js:619` — `$(document).on('click', '#box-color-variants .wariant_kolorystyczny', ...)` — zamyka modal w starym layoucie. W nowym layoucie ten selektor się nie dopasowuje (i dobrze — nowy wygląd nie jest modalem).
</context>
<acceptance_criteria>
## AC-1: Wizualny kształt bloku wariantów
```gherkin
Given jestem klientem korzystającym z IP 89.69.31.86
And otwieram stronę dowolnego produktu z kilkoma wariantami kolorystycznymi
When patrzę na sekcję "Wybierz wersję kolorystyczną"
Then widzę miniatury wariantów ułożone w siatce / rzędzie (min. 3 kafelki w linii na szerokim ekranie)
And każdy kafelek jest kwadratowy i zawiera obraz tekstury wariantu
And pod każdym kafelkiem jest widoczny podpis z nazwą wariantu
And aktywny (wybrany) wariant jest wizualnie wyróżniony (np. obramowanie, tło)
And układ jest responsywny (co najmniej 2 kafelki w linii poniżej 768 px)
```
## AC-2: Klik zmienia wariant produktu
```gherkin
Given jestem klientem korzystającym z IP 89.69.31.86
And otwieram stronę produktu w nowym layoucie
When klikam miniaturę innego wariantu niż aktualnie zaznaczony
Then PrestaShop przełącza na wybraną kombinację (URL / cena / obrazy produktu aktualizują się tak samo jak w starym layoucie)
And klikany wariant staje się wariantem aktywnym (wizualne wyróżnienie)
And żadne błędy JS nie pojawiają się w konsoli
```
## AC-3: Brak regresji w starym layoucie i innych funkcjach nowego
```gherkin
Given jestem klientem z IP innym niż 89.69.31.86
When otwieram stronę produktu
Then stary layout wygląda i działa identycznie jak przed zmianami (wariant kolorystyczny w modalu #box-color-variants, konfigurator rozmiaru, odbicie, dodanie do koszyka)
Given jestem klientem z IP 89.69.31.86 w nowym layoucie
When używam innych elementów strony produktu (konfigurator rozmiaru, przyciski, zakładki opisu)
Then elementy te zachowują się co najmniej tak samo jak przed zmianami w tym planie (nie zepsute dodatkowo)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Dostosuj markup bloku product-variants w nowym layoucie (product.tpl)</name>
<files>themes/ayon/templates/catalog/product.tpl</files>
<action>
W gałęzi `{if $smarty.server.REMOTE_ADDR == '89.69.31.86'}` (okolice linii 604607) wokół `{include file='catalog/_partials/product-variants.tpl'}` dla `$product_variant_mode = 2` dodaj pomocniczy kontener-klasę, która pozwoli stargetować nowy wygląd w CSS bez ruszania partiala i starego kodu. Struktura docelowa:
```smarty
{block name='product_variants'}
{$product_variant_mode = 2}
<div class="product-box product-variants-data product-variants-data--new">
<h4 class="block-title">Wybierz wersję kolorystyczną</h4>
<div class="product-variants-grid">
{include file='catalog/_partials/product-variants.tpl'}
</div>
</div>
{/block}
```
Wymagania:
- NIE zmieniaj partiala `product-variants.tpl` (dzielony ze starym layoutem).
- NIE zmieniaj gałęzi `{if ... != '89.69.31.86'}` — stary layout nietknięty.
- Owinięcie w `.product-variants-grid` daje nam czysty scope CSS tylko dla nowego widoku.
- W partialu dla mode=2 jest `{if $id_attribute_group == 5}<a ... class="fancybox-material-controls">` — w mode=2 ten warunek nie dotyczy, więc OK.
Avoid: modyfikowania partiala, starej ścieżki, innych sekcji `product.tpl`.
</action>
<verify>
`git diff themes/ayon/templates/catalog/product.tpl` pokazuje zmianę TYLKO w bloku między liniami `{if $smarty.server.REMOTE_ADDR == '89.69.31.86'}` a `{/if}` (ta druga linia ~800+). Brak zmian w gałęzi `!=`.
</verify>
<done>AC-1 przygotowane strukturalnie (markup gotowy pod CSS); AC-3 niezagrożone (stary layout nie ruszony).</done>
</task>
<task type="auto">
<name>Task 2: Stwórz style SCSS/CSS dla .product-variants-data--new (kafelki 3-w-rzędzie)</name>
<files>themes/ayon/assets/css/custom.scss, themes/ayon/assets/css/custom.css</files>
<action>
W `custom.scss` dodaj blok (na końcu pliku lub w sekcji poświęconej stronie produktu):
```scss
/* NEW product page — color variants grid (IP-gated layout) */
body#product .product-variants-data--new {
.product-variants-grid > .product-variants {
display: block;
margin: 0;
}
.product-variants-grid ul[id^="group_"] {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
@media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
}
.product-variants-grid li.wariant_kolorystyczny {
float: none;
width: auto;
margin: 0;
padding: 0;
}
.product-variants-grid li.wariant_kolorystyczny > label {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
cursor: pointer;
border: 2px solid transparent;
border-radius: 4px;
padding: 6px;
transition: border-color .15s ease-in-out;
/* ukryj natywny radio — wybór po kliknięciu labela */
input.input-color {
position: absolute;
opacity: 0;
pointer-events: none;
}
img {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
display: block;
}
/* podpis = ukryty span.sr-only.tip z nazwą wariantu — pokaż go */
.sr-only.tip {
position: static;
width: auto;
height: auto;
clip: auto;
overflow: visible;
white-space: normal;
font-size: 14px;
text-align: center;
color: #3c3c3c;
}
/* kwadracik koloru / tekstury ze starego markupu — w nowym widoku ukryty,
bo prezentujemy sam obraz wariantu */
> span.color { display: none; }
&:hover { border-color: #c9bda4; }
}
.product-variants-grid li.wariant_kolorystyczny > label:has(input.input-color:checked) {
border-color: #7d6e4f;
}
}
```
Następnie zregeneruj `custom.css`:
- Jeśli w projekcie jest pipeline SCSS → uruchom kompilację.
- Jeśli nie — odpowiadający blok CSS (rozwinięty, bez zagnieżdżeń) dopisz ręcznie na końcu `custom.css` tak, by trafił do produkcji.
Avoid:
- Nadpisywania reguł globalnych `.product-variants`, `.wariant_kolorystyczny` bez scope'a `body#product .product-variants-data--new` — zepsułoby to stary layout i quickview.
- Usuwania istniejących reguł w custom.css/custom.scss.
</action>
<verify>
1. Zaloguj się z IP 89.69.31.86, otwórz produkt z ≥3 wariantami.
2. Wizualnie: 3 kafelki obok siebie, kwadratowe obrazy, podpisy pod spodem, aktywny wyróżniony obramowaniem.
3. Stary layout (inne IP) w tabeli wariantów wygląda tak jak przed zmianą.
</verify>
<done>AC-1 spełnione; AC-3 (część wizualna starego layoutu) utrzymane.</done>
</task>
<task type="auto">
<name>Task 3: Zapewnij zmianę wariantu po kliknięciu w nowym layoucie (custom.js)</name>
<files>themes/ayon/assets/js/custom.js</files>
<action>
PrestaShop core.js natywnie nasłuchuje `change` na `input[name^="group["]` — klik w `<label>` powinien przełączyć radio i wywołać refresh wariantu. Problem pojawia się, gdy po refreshu AJAX kontener `.product-variants` jest podmieniany i bezpośrednie bindingi (np. `custom.js:225 jQuery(".wariant_kolorystyczny").click(...)`) giną.
Dodaj na końcu pliku `themes/ayon/assets/js/custom.js` (po istniejącym handlerze `#box-color-variants .wariant_kolorystyczny`) delegowany listener gwarantujący działanie również w nowym layoucie:
```js
/* NEW layout — klik w kafelek wariantu zmienia wariant (delegowany, przeżywa refresh AJAX) */
$(document).on('click', '.product-variants-data--new .wariant_kolorystyczny label', function (e) {
var $label = $(this);
var $radio = $label.find('input.input-color');
if (!$radio.length) return;
if ($radio.is(':checked')) return; // już aktywny
$radio.prop('checked', true).trigger('change');
});
```
Uwaga:
- NIE zmieniaj istniejącego handlera `jQuery(".wariant_kolorystyczny").click(...)` (linia ~225) — obsługuje logikę „piece" i działa w starym layoucie.
- NIE zmieniaj handlera `#box-color-variants .wariant_kolorystyczny` (linia ~619) — zamyka modal w starym layoucie.
- Scope `.product-variants-data--new` (dodany w Task 1) zapewnia, że nowy kod nie dotyka starego layoutu.
- `trigger('change')` na radio uruchamia natywny mechanizm PrestaShop do przełączenia wariantu.
Avoid: `e.preventDefault()` na labelu (zablokowałoby natywny toggle radia).
</action>
<verify>
1. IP 89.69.31.86 → strona produktu z wariantami → otwórz DevTools → Console.
2. Kliknij inny wariant niż aktualny → strona powinna zaktualizować wariant (URL, cena, zdjęcia) bez błędów JS.
3. Kliknij ponownie ten sam wariant → nic się nie zmienia (brak zbędnych przeładowań).
</verify>
<done>AC-2 spełnione; AC-3 utrzymane (stara ścieżka nietknięta).</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Nowy wygląd bloku wariantów kolorystycznych na stronie produktu (IP 89.69.31.86):
- 3 kafelki obok siebie z obrazem + podpisem,
- klik w kafelek przełącza wariant produktu,
- stary layout (inne IP) nie zmieniony.
</what-built>
<how-to-verify>
1. Jako klient z IP 89.69.31.86 wejdź na stronę dowolnego produktu z kilkoma wariantami kolorystycznymi.
2. Sprawdź wizualnie: sekcja „Wybierz wersję kolorystyczną" ma wyglądać jak w `d:\temp\Frame 33.png` (3 kafelki w rzędzie, podpisy pod spodem).
3. Sprawdź na mniejszym oknie (poniżej 768 px): kafelki układają się w 2 kolumny.
4. Kliknij inny wariant niż aktywny → potwierdź: URL się zmienia, cena i obrazy produktu się aktualizują, klikany kafelek staje się wyróżniony.
5. Otwórz konsolę DevTools — brak błędów JS.
6. Wejdź na tę samą stronę spoza IP 89.69.31.86 → potwierdź: stary layout (modal #box-color-variants) wygląda i działa jak przed zmianami.
7. Na stronie produktu z IP 89.69.31.86 sprawdź, że pozostałe elementy (konfigurator rozmiaru, odbicie, dodanie do koszyka) działają co najmniej tak samo jak przed zmianą.
</how-to-verify>
<resume-signal>Wpisz "approved" aby zamknąć plan lub opisz problemy do poprawy.</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Gałąź `{if $smarty.server.REMOTE_ADDR != '89.69.31.86'}` w `product.tpl` (stary layout produkcyjny).
- `themes/ayon/templates/catalog/_partials/product-variants.tpl` (partial wspólny dla starego i nowego + quickview).
- Istniejące handlery `jQuery(".wariant_kolorystyczny").click(...)` (custom.js ~225) i `$(document).on('click', '#box-color-variants .wariant_kolorystyczny', ...)` (custom.js ~619).
- Globalne reguły CSS dla `.product-variants`, `.wariant_kolorystyczny`, `#box-color-variants` w `custom.scss` / `custom.css` — nowy CSS dopisywany TYLKO pod scope `.product-variants-data--new` (lub `body#product .product-variants-data--new`).
- PrestaShop core (`themes/ayon/assets/js/theme.js`, bundles).
## SCOPE LIMITS
- Ten plan naprawia wyłącznie sekcję wariantów kolorystycznych w nowym layoucie.
- Nie naprawiamy w tym planie: konfiguratora rozmiaru, odbicia lustrzanego, ceny, dodania do koszyka, zakładek, próbki, struktur — nawet jeśli są popsute (idą do Phase 02).
- Nie dodajemy nowych zależności (brak npm / composer install).
- Nie zmieniamy backendu (kontrolery, moduły).
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] `git diff` pokazuje zmiany wyłącznie w: `product.tpl` (gałąź nowego layoutu), `custom.scss`, `custom.css`, `custom.js`.
- [ ] Test ręczny z IP 89.69.31.86 — AC-1, AC-2 spełnione.
- [ ] Test ręczny z innym IP — AC-3 część „stary layout" OK.
- [ ] Brak błędów JS w konsoli na stronie produktu w obu trybach.
- [ ] Checkpoint `human-verify` zatwierdzony przez użytkownika ("approved").
</verification>
<success_criteria>
- AC-1, AC-2, AC-3 spełnione.
- Checkpoint zatwierdzony.
- Brak regresji w partialu `product-variants.tpl` i starym layoucie.
</success_criteria>
<output>
Po zakończeniu planu utwórz `.paul/phases/01-product-variants-fix/01-01-SUMMARY.md` z:
- listą zmodyfikowanych plików (z krótkim opisem zmiany),
- decyzjami (np. dlaczego scope `.product-variants-data--new` zamiast globalnego),
- potencjalnymi side-effectami do obserwacji w Phase 02.
</output>

View File

@@ -0,0 +1,182 @@
---
phase: 01-product-variants-fix
plan: 01
subsystem: ui
tags: [prestashop, smarty, jquery, scss, product-variants, ajax-refresh]
requires: []
provides:
- Działający wariant kolorystyczny w nowym layoucie strony produktu (IP-gated `== '89.69.31.86'`)
- Grid 3×1 (desktop) / 2×1 (<768 px) kafelków wariantów wg Figma 27:9867
- In-place refresh wariantu (obraz + cena + URL) bez przeładowania strony
affects:
- Phase 02+ — kolejne naprawy nowego layoutu (add-to-cart, konfigurator rozmiaru, odbicie, zakładki)
tech-stack:
added: []
patterns:
- "Scoped CSS pod body#product .product-variants-data--new — izoluje nowy layout od globalnych reguł"
- "Własny AJAX PS refresh (action=refresh) + manual DOM replace jako fallback gdy PS core handler nie znajduje wymaganych selektorów"
- "Dwa IP-gated branche w product.tpl renderują się wzajemnie wyłącznie — duplikaty ID (np. add-to-cart-or-refresh) OK"
key-files:
created:
- .paul/PROJECT.md
- .paul/ROADMAP.md
- .paul/STATE.md
- .paul/phases/01-product-variants-fix/01-01-PLAN.md
- .claude/memory/feedback_scss_only.md
- .claude/memory/MEMORY.md
modified:
- themes/ayon/templates/catalog/product.tpl
- themes/ayon/assets/css/custom.scss
- themes/ayon/assets/js/custom.js
key-decisions:
- "Ręczny AJAX refresh w custom.js zamiast fake .product-actions — unika kolizji stylów ze starym layoutem"
- "action=refresh (nie productrefresh) — odkryte empirycznie przez probing endpointów"
- "Edytuj tylko custom.scss, nie custom.css — user ma lokalny watcher SCSS (zapisane jako feedback memory)"
- "In-place update (history.pushState + manual DOM replace) zamiast full redirect — user feedback o starym layoucie"
patterns-established:
- "IP-gated layout switching: nowy layout pod REMOTE_ADDR == '89.69.31.86', stary dla reszty. Zmiany CSS/JS scope'owane pod .product-variants-data--new aby nie dotykać starego layoutu"
- "Fallback graceful: próba in-place update → przy wyjątku/błędzie HTTP fallback na window.location.href = resp.product_url lub reload"
duration: ~2h
started: 2026-04-23T17:50:00Z
completed: 2026-04-23T18:25:00Z
---
# Phase 01 Plan 01: Product variants (nowy layout) — Summary
**Wariant kolorystyczny w nowym layoucie: 3-kafelkowy grid wg Figma + klik zmienia kombinację produktu w miejscu (AJAX refresh, bez reloadu).**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~2h |
| Started | 2026-04-23T17:50:00Z |
| Completed | 2026-04-23T18:25:00Z |
| Tasks | 3 auto + 1 checkpoint (ok) |
| Files modified | 3 source + 6 PAUL/memory meta |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Wizualny kształt bloku wariantów (3 kafelki, responsive) | Pass | Grid 3×1 desktop / 2×1 <768 px; Inter 14/25 `#8c8c8c` left-aligned; active outline `#7d6e4f`. Zgodnie z Figma 27:9867. |
| AC-2: Klik zmienia wariant produktu (URL/cena/obrazy) | Pass | Test live w Playwright: klik wariant 2 → obraz `919/...``920/...`, URL `202-2306-...``202-2289-...`, bez reloadu. |
| AC-3: Brak regresji w starym layoucie i innych funkcjach | Pass | Stara gałąź `!= '89.69.31.86'` nietknięta, partial `product-variants.tpl` nietknięty, istniejące handlery w custom.js (`~225`, `~619`) nietknięte, CSS scope'owany. |
## Accomplishments
- Dopasowany wygląd sekcji wariantów w nowym layoucie do makiety Figma (3-kafelkowy grid z podpisami, minimalistyczny styling).
- Przywrócona funkcjonalność zmiany wariantu (nowy layout nie miał `<form id="add-to-cart-or-refresh">`) + zaimplementowany flow in-place refresh bez reloadu, lepsze UX niż pełne przekierowanie.
- Zbudowany szkielet PAUL (PROJECT/ROADMAP/STATE + phase dir) w projekcie, który ułatwi kolejne fazy.
## Task Commits
Task commits nie zostały jeszcze utworzone — APPLY był wykonywany inline bez per-task commitów (delegation: off). Commit fazowy wykona `transition-phase` (pending user approval).
| Task | Commit | Type | Description |
|------|--------|------|-------------|
| Task 1 (markup) | pending | feat | Wrap bloku wariantów w `.product-variants-data--new` + `<form#add-to-cart-or-refresh>` w product.tpl (nowa gałąź) |
| Task 2 (CSS) | pending | feat | SCSS grid pod `body#product .product-variants-data--new` wg Figma |
| Task 3 (JS) | pending | feat | Delegowany click handler + własny AJAX refresh z in-place DOM update w custom.js |
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `themes/ayon/templates/catalog/product.tpl` | Modified | Nowa gałąź `REMOTE_ADDR == '89.69.31.86'`, blok `product_variants` (ok. linia 601) — dodany wrapper `.product-variants-data--new`, `<form#add-to-cart-or-refresh>` z hidden inputs `token` / `id_product` / `id_customization`, grid kontener `.product-variants-grid` |
| `themes/ayon/assets/css/custom.scss` | Modified | Dopisany blok `body#product .product-variants-data--new {...}` — grid, typography Inter 14/25 #8c8c8c left, active outline |
| `themes/ayon/assets/js/custom.js` | Modified | Dopisane 2 delegowane handlery: click label (toggle radio) + change radio (AJAX `action=refresh` → pushState + replace `.product-prices`, `.product_image_wrapper` + emit `updatedProduct`) |
| `.paul/PROJECT.md` | Created | Bootstrap PAUL — opis projektu, constraints (IP-gated layout, shared partial) |
| `.paul/ROADMAP.md` | Created | Milestone v0.1 — 2 fazy (Phase 01 wariant kolorystyczny; Phase 02 pozostałe funkcje) |
| `.paul/STATE.md` | Created + updated | Loop position tracking |
| `.paul/phases/01-product-variants-fix/01-01-PLAN.md` | Created | Plan fazy 1 |
| `.paul/phases/01-product-variants-fix/01-01-SUMMARY.md` | Created | Ten plik |
| `.claude/memory/feedback_scss_only.md` | Created | Feedback memory: edytuj tylko custom.scss |
| `.claude/memory/MEMORY.md` | Created | Index memory |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Dodanie `<form#add-to-cart-or-refresh>` w gałęzi nowego layoutu (scope creep względem PLAN) | Bez niego PS serializuje pustą formę i AJAX nie zna `group[X]=Y`. Bez tego nie da się zrealizować AC-2. | Minimalny + potrzebny. Przygotowuje fundament pod Phase 02 (ta sama forma może być używana do add-to-cart). |
| Ręczny AJAX refresh w custom.js (nie zmuszanie PS core handler do działania przez dodawanie klasy `.product-actions`) | PS core handler `updateProduct_` szuka formy przez `$('.product-actions').find('form:first')`. Dodanie tej klasy do mojego wrappera mogłoby wciągnąć style starego layoutu i kolizje. | Bezpieczniejsze, bardziej deterministyczne, w pełni pod kontrolą. |
| `action=refresh` (nie `productrefresh`) | Empiryczne probowanie endpointów w Playwright: `productrefresh` zwracał pustą odpowiedź, `refresh` zwraca JSON z fragmentami HTML. | Wiedza do reużycia w Phase 02. |
| In-place DOM update z `history.pushState` (vs. `window.location.href = resp.product_url` reload) | User feedback: „na starym layoucie działało bez przeładowania i wyglądało lepiej". | Lepsze UX. Fallback na reload przy błędzie. |
| Edytuj tylko `custom.scss`, nie `custom.css` | User ma lokalny watcher SCSS → lokalne zmiany w `custom.css` nadpisane przy rekompilacji. | Zapisane w feedback memory — przyszłe sesje zastosują regułę automatycznie. |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 2 | Niezbędne do spełnienia AC-2 |
| Scope additions | 1 | Konieczne (form wrapper) — uzgodnione z userem |
| Deferred | 1 | Pełny wizualny fit — dociągnie kompilacja SCSS |
**Total impact:** Zero scope creep poza niezbędnymi do działania AC-2. Wszystkie odstępstwa zaakceptowane przez usera w trakcie.
### Auto-fixed Issues
**1. Brak `<form id="add-to-cart-or-refresh">` w nowym layoucie — blokada AJAX refresh**
- **Found during:** Live test w Playwright po Task 3, klik wariantu nie triggerował AJAX.
- **Issue:** PS serializuje formę o tym ID; bez niej puste body → serwer nie zna `group[X]=Y`.
- **Fix:** Dodany form w bloku `product_variants` w nowym layoucie, z hidden inputs (`token`, `id_product`, `id_customization`). Scope creep uzgodniony z userem (odpowiedź „1. Apply. Tylko na razie obie wersje muszą działać jednocześnie").
- **Files:** `themes/ayon/templates/catalog/product.tpl`
- **Verification:** `$('#add-to-cart-or-refresh').serialize()` w DevTools zwraca `token=...&id_product=202&...&group%5B4%5D=5`.
**2. PS core handler `updateProduct_` szuka formy w `.product-actions` — nie istnieje w nowym layoucie**
- **Found during:** Live test w Playwright, klik triggerował `change` ale PS handler nie wysyłał AJAX.
- **Issue:** Handler: `var t = $(".product-actions"); ...$.ajax({url: t.find('form:first').attr('action')...})``t` pusty w nowym layoucie.
- **Fix:** Dopisany własny delegowany `change` handler w `custom.js` który robi własny POST (`action=refresh`) na bieżący URL produktu i aktualizuje DOM in-place.
- **Files:** `themes/ayon/assets/js/custom.js`
- **Verification:** Playwright test — klik wariant 2, AJAX `200 OK` + JSON, obraz i URL się zmieniają bez reloadu.
### Scope Additions
**1. Dodanie wrapper'a `<form id="add-to-cart-or-refresh">` w product.tpl**
- Plan mówił „nie naprawiamy add-to-cart". Ale bez formy nie ma AC-2. Uzgodnione z userem przed APPLY: „1. Apply. Tylko na razie obie wersje muszą działać jednocześnie".
### Deferred Items
Logged do STATE.md „Open observations":
- Brak `.product-actions` wokół `product_add_to_cart` → „Dodaj do koszyka" prawdopodobnie nie działa w nowym layoucie.
- Puste bloki: `.product-size-data`, `.product-protect`, `.product-installation`, `.product-order-sample` — do implementacji w Phase 02+.
- Konfigurator „piece" (crop + mirror) — brak markup'u w nowym layoucie.
- Obserwacja dla wizualnego dopasowania: po rekompilacji SCSS warto sprawdzić czy globalne reguły `.product-variants` / `.wariant_kolorystyczny` nie dominują nad scope'owymi (edge case visual).
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| nginx cache'uje `custom.js` bez cache-bustera — Playwright widzi starą wersję przy `fetch('/custom.js')` | Weryfikacja przez `fetch` z query busterem `?v=timestamp`; user czyści cache PS + hard refresh przeglądarki (`Ctrl+F5`). |
| Figma OAuth URL początkowo dawał błąd „Parameter code_challenge_method is required" | User otworzył URL po ponownym wygenerowaniu; jednak drugi `authenticate` unieważnił pierwszą sesję — CSRF mismatch. Rozwiązanie: trzeci `authenticate` i dokładnie jednorazowe użycie linka. |
| `action=productrefresh` zwracał pustą odpowiedź (200 OK + empty body) | Empiryczne probowanie wariantów: `action=refresh` zwraca poprawny JSON. |
| Prestashop emituje `updateProduct_` (z podkreślnikiem), nie `updateProduct` — handler dla `updateProduct_` szuka formy w niewłaściwym miejscu | Obejście: własny delegowany change handler robi niezależny AJAX; `prestashop.emit('updatedProduct', resp)` nadal uruchamia rejestrowane handlery theme.js. |
## Skill audit
SPECIAL-FLOWS.md nie istnieje w projekcie → brak wymaganych skill'i. Użyte ad-hoc: `figma:figma-implement-design` (podgląd spec frame 27:9867), Playwright (live testing na produkcji). ✓
## Next Phase Readiness
**Ready:**
- Działa IP-gated switch między starym a nowym layoutem — pattern do reużycia w kolejnych naprawach.
- Pattern własnego AJAX refresh (`action=refresh` + `$.ajax` + `pushState` + manual DOM replace) — gotowy do zastosowania przy kolejnych interakcjach wariant-zależnych.
- Mapa brakujących elementów w nowym layoucie (patrz „Deferred Items" + STATE.md „Open observations").
**Concerns:**
- Wizualny 1:1 fit z Figma może wymagać drobnych poprawek po rekompilacji SCSS (globalne reguły `.product-variants` mogą dominować).
- CSS dla aktywnego wariantu (outline) to minimalistyczna interpretacja — Figma nie pokazywała active state; użytkownik może chcieć inne (np. tick icon, gruba ramka).
**Blockers:** None.
---
*Phase: 01-product-variants-fix, Plan: 01*
*Completed: 2026-04-23*