Files
newwalls.pl/.paul/phases/02-product-actions-fixes/02-01-SUMMARY.md
Jacek Pyziak 7ac795ba3f 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>
2026-04-23 23:33:45 +02:00

218 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 02-product-actions-fixes
plan: 01
subsystem: ui
tags: [prestashop, smarty, jquery, scss, piece, crop, fancybox, module-hook-compat]
requires:
- phase: 01-product-variants-fix
provides:
- Form `<form id="add-to-cart-or-refresh">` w gałęzi nowego layoutu — Plan 02-01 dołącza do niej 8 hidden inputs dla crop/mirror
- Marker class `.product-variants-data--new` — Plan 02-01 wykorzystuje do scope'u JS init (`setTimeout` check)
- Własny AJAX refresh w custom.js (`action=refresh` + in-place DOM update) — Plan 02-01 dopina listener `prestashop.on('updatedProduct')` do re-bindowania dragElement po replace `.product_image_wrapper`
provides:
- Działający konfigurator „piece" (wybór fragmentu tapety) w nowym layoucie: popup trigger w `.product-size-data`, fancybox z input'ami Szerokość/Wysokość, piece overlay na zdjęciu
- 8 hidden inputów `is_crop`, `is_reflection`, `crop_pos_x/y`, `crop_width/height`, `piece_bg_top/left` w formie `#add-to-cart-or-refresh` (nowy layout)
- Defensive setup (no-op override `totalpriceinfospecific`/`prod` + DOM stubs w custom.js) — unblocks istniejące piece handlery w nowym layoucie bez modyfikacji inline-scriptów z module hooks
affects:
- Plan 02-02 (add-to-cart) — hidden inputs już w formie, gotowe do POST'owania do koszyka. Także no-op override `totalpriceinfospecific` oznacza że logika cen wymaga innego mechanizmu w nowym layoucie.
- Plan 02-03 (empty blocks) — pattern scope'owania CSS pod `.product-size-data .product-size-data--new` do reużycia dla `.product-protect`, `.product-installation`, `.product-order-sample`
tech-stack:
added: []
patterns:
- "Shared partial reuse: zamiast duplikować element między layoutami, reużyć istniejący (#piece z product-cover-thumbnails.tpl)"
- "No-op override globalnych funkcji z module hook (`totalpriceinfospecific`, `prod`) jako alternative do modyfikacji inline-scriptów których nie można zmienić"
- "DOM stubs injection via JS zamiast template — obchodzi Smarty cache + FTP sync delay"
- "setTimeout(600) init zamiast `$(document).ready` — defer poza queue ready callbacków które crash'ują na brakujących DOM elementach w nowym layoucie"
key-files:
created:
- .paul/phases/02-product-actions-fixes/02-01-PLAN.md
- .paul/phases/02-product-actions-fixes/02-01-SUMMARY.md
modified:
- themes/ayon/templates/catalog/product.tpl
- themes/ayon/assets/js/custom.js
- themes/ayon/assets/css/custom.scss
key-decisions:
- "`#piece` reużywany z product-cover-thumbnails.tpl — nie duplikujemy w product.tpl"
- "totalpriceinfospecific / prod override'owane na no-op w nowym layoucie (crash z powodu brakujących DOM deps)"
- "DOM stubs injection w JS zamiast template (cache/sync unreliability)"
- "Piece NIE auto-init'uje się — pojawia się dopiero po kliknięciu popup trigger (user feedback w checkpoint)"
- "Defensive guard `.pp_stick_parent` w click handler (element exists only w starym layoucie)"
patterns-established:
- "Scope'owanie CSS dla bloków nowego layoutu: `body#product .<block-class> .<block-class>--new {...}` — wzór do reużycia dla kolejnych bloków (product-protect, product-installation, etc.)"
- "Obrona przed cross-layout breakage module hooków: setTimeout init w custom.js z no-op override + DOM stubs; nie ruszamy inline-scriptów"
- "Reużywanie state holders jako hidden inputów: `<input type='checkbox' style='display:none'>` zamiast tworzenia osobnego state-management, istniejące handlery bindują się do ID identycznych ze starym layoutem"
duration: ~3.5h (z live debug'iem via Playwright)
started: 2026-04-23T19:30:00Z
completed: 2026-04-23T22:30:00Z
---
# Phase 02 Plan 01: Piece/crop configurator (nowy layout) — Summary
**Port konfiguratora „piece" (wybór fragmentu tapety + mirror) do nowego layoutu z zachowaniem pełnego kontraktu serializacji do koszyka. Popup trigger + fancybox — odzwierciedla user flow starego layoutu.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~3.5h |
| Started | 2026-04-23T19:30:00Z |
| Completed | 2026-04-23T22:30:00Z |
| Tasks | 3 auto + 1 checkpoint (approved po iteracji) |
| Files modified | 3 source + 2 PAUL meta |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Markup piece + mirror w nowym layoucie | Pass | `#piece` reużyty z shared partial (nie duplikowany), controls w `.product-size-data .product-box--data`, 8 hidden inputów w `#add-to-cart-or-refresh`. Initial text „Wybierz rozmiar" (zamiast stałego „100x100" per user feedback). |
| AC-2: Piece interaktywny | Pass | Popup otwiera się po kliknięciu `.piece-summary`, zmiana wymiarów w popup → piece rescale (100x100 → 200x150 = 40%×50% kontenera), hidden inputs `#product_crop_width/height` sync'ują. Drag działa (dragElement z istniejącego flow). Verified via Playwright. |
| AC-3: Form serialization | Pass | `$('#add-to-cart-or-refresh').serialize()` zwraca pełny ciąg: `token=...&id_product=...&id_customization=...&is_crop=1&is_reflection=0&crop_pos_x=200&crop_pos_y=100&crop_width=200&crop_height=150&piece_bg_top=-104.398&piece_bg_left=-208.804&group[4]=6`. Wszystkie pola aktualne. |
| AC-4: Re-init po zmianie wariantu | Not tested | AC zakładał re-bind dragElement po `updatedProduct`. Listener dodany (custom.js:681-692), ale live test zmiany wariantu nie przeprowadzony w tej sesji. Markowane jako "not verified" — do re-verify w Plan 02-02 lub osobno. |
| AC-5: Zero regresji w starym layoucie | Not tested | Stary layout (IP != 89.69.31.86) nie był testowany w tej sesji. Zmiany w custom.js (no-op override, setTimeout init, stubs injection) są scope'owane do nowego layoutu via `$('.product-variants-data--new').length` check — nie powinny dotknąć starego. Do weryfikacji przed commitowaniem phase. |
**Modyfikacja AC:** Plan pierwotnie zakładał piece auto-init'ujący się na load (inline `<style>` force visibility + `checkedHandler` w setTimeout). User feedback w trakcie checkpoint zmienił wymaganie: piece pojawia się dopiero po kliknięciu trigger. AC-2 satisfied zgodnie z tym zmodyfikowanym flow.
## Accomplishments
- **Port piece configurator z zachowaniem kontraktu** — identyczne ID i nazwy hidden inputów ze starym layoutem → istniejące JS handlers bindują się automatycznie, serwer-side logic nie wymaga zmian.
- **Popup match z Figma/screenshot** — istniejący fancybox flow (custom.js:535-557) działa w nowym layoucie po dodaniu trigger'a `<a class="fancybox-size-controls piece-summary">` i defensywnych guard'ów.
- **Rozwiązanie 5 ukrytych problemów wykrytych w live debug** (Playwright): duplikat `#piece`, CSS `:has()` edge case, ready queue fighting, inline-script crash, brakujący `.pp_stick_parent`. Każdy udokumentowany w Decisions.
- **Pattern defensywnego setup'u dla module hook compatibility** — no-op override + DOM stubs w JS zamiast template. Reużywalny przy naprawach innych funkcji nowego layoutu.
## Task Commits
Task commits nie zostały jeszcze utworzone — APPLY wykonywany inline (delegation: off) bez per-task commitów. Commit fazowy wykona `transition-phase` po zakończeniu całej fazy 02 (plany 02-01, 02-02, 02-03+).
| Task | Commit | Type | Description |
|------|--------|------|-------------|
| Task 1 (markup) | pending | feat | Piece trigger + controls + 8 hidden inputs w gałęzi nowego layoutu |
| Task 2 (JS) | pending | feat | setTimeout init z no-op override + stubs, `prestashop.on('updatedProduct')` listener, defensive fancybox guard |
| Task 3 (CSS) | pending | feat | Scoped styles `.product-size-data--new`, `.piece-summary`, `#button-mirror-reflection` |
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `themes/ayon/templates/catalog/product.tpl` | Modified | Gałąź `== '89.69.31.86'`: 8 hidden inputs w `#add-to-cart-or-refresh`, `.piece-summary` anchor w `.product-size-data .product-box--data`, wrapper `.product-size-data--new`, mirror button, hidden state (checkbox, width/height, position divs). Initial `#piece-size-view` text = „Wybierz rozmiar". |
| `themes/ayon/assets/js/custom.js` | Modified | +~60 linii: `prestashop.on('updatedProduct', ...)` listener do re-bindu dragElement po AJAX refresh, setTimeout(600) init w nowym layoucie (override totalpriceinfospecific/prod na no-op, wstrzyknięcie DOM stubs), defensive `.pp_stick_parent` guard w `.fancybox-size-controls` click handler (ok. linia 540). |
| `themes/ayon/assets/css/custom.scss` | Modified | +~60 linii na końcu pliku: Phase 02 scope block pod `body#product .product-size-data .product-size-data--new` (flex layout, piece-summary trigger styling, mirror button hover). Istniejące `.product-images .piece` ze starego layoutu applikują się automatycznie do `#piece` z shared partial. |
| `.paul/phases/02-product-actions-fixes/02-01-PLAN.md` | Created | Plan fazy 02 plan 01 |
| `.paul/phases/02-product-actions-fixes/02-01-SUMMARY.md` | Created | Ten plik |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Reuse `#piece` z `product-cover-thumbnails.tpl` (shared partial), NIE duplikować | Pierwotna próba dodania osobnego `<div id="piece">` jako sibling `.product_image_wrapper` tworzyła duplikat ID → two elements z tym samym ID → my element renderował biała pusta ramkę, oryginalny (z pattern bg) był niewidoczny gdzie indziej. | Mniej markup, reużycie istniejącego styling (`.product-images .piece` ze starego SCSS). |
| Override `totalpriceinfospecific` / `prod` na no-op w custom.js setTimeout | Inline-script z module hook (squaremeter) czyta `#totalpriceinfo.style`, `#product-details.data()`, robi `JSON.parse(undefined)` — wszystko null/undefined w nowym layoucie. Error propaguje przez `trigger('change')` na `#piece-width` i abortuje `checkedHandler` przed `is_crop=1`. | `checkedHandler` działa w nowym layoucie. Cena calculation wyłączona — trzeba zapewnić alternatywny mechanizm w Plan 02-02. |
| DOM stubs (`#totalpriceinfo`, etc.) wstrzykiwane przez JS, nie przez template | Smarty template cache + ręczny FTP sync powodował że stuby w `product.tpl` nie docierały do browsera (page wciąż używała starej skompilowanej wersji templatu). JS deploys są bardziej deterministyczne (pojedynczy plik, jasne cache semantics). | Stuby zawsze w DOM w nowym layoucie. Template pozostaje czysty. |
| Piece NIE auto-init'uje się na load (user feedback w checkpoint) | User: „kwadrat pojawia się dopiero po kliknieciu przycisku cm — kliknij aby zmienić". Zmiana zachowania vs pierwotny plan. | Load = placeholder + hidden piece. Click = popup + piece fadeIn. Oszczedność initial clutter na zdjęciu. |
| Defensive guard `.pp_stick_parent` w fancybox-size-controls click handler | Element istnieje tylko w starym layoucie (PS sidebar). Bez guard'a `$('.pp_stick_parent').offset().top` rzuca TypeError, aborts handler przed `$.fancybox()`. | Popup działa w obu layoutach. Old layout nieruszony (guard = if empty → skip). |
| `piece-width/height` hidden state jako `style="display:none;"` inputs w markup (nie w popup JS template) | Istniejące JS handlery (custom.js:275-300) bindują się do ID `#piece-width`/`#piece-height` przez direct binding. Muszą istnieć w DOM at ready time. Popup używa `#fancy-piece-width/height` i sync'uje do hidden state on confirm. | Zero zmian w istniejącej logice. Clean separation: popup UI × state inputs. |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 5 | Niezbędne — bez nich piece nie działał w nowym layoucie |
| Scope additions | 3 | Defensive setup (override + stubs + guard) — wymagane przez module hook compat |
| Deferred | 1 | AC-5 (stary layout regresja test) + AC-4 (variant AJAX re-init) nie zweryfikowane w tej sesji |
**Total impact:** Plan wykonany z istotnymi auto-fixami znalezionymi przez Playwright live debug. Zero scope creep poza niezbędnymi do działania. Zmiana user-facing behavior (piece auto-init → click-to-show) uzgodniona z userem w trakcie checkpoint.
### Auto-fixed Issues
**1. Duplikat `#piece` — dwa elementy z tym samym ID**
- **Found during:** Task 1 (markup) → live Playwright snapshot wykrył `pieceCount: 1` ale `pieceStyles.backgroundImage: url(".../birds-in-the-fog.jpg")` — element pochodzi ze shared partial `product-cover-thumbnails.tpl:53`.
- **Issue:** Dodałem własny `<div id="piece">` jako sibling `.product_image_wrapper`. Powstał duplikat ID (invalid HTML + konflikt JS/CSS).
- **Fix:** Usunięty własny `<div id="piece">`. Reużywamy istniejący z partial. Usunięty marker class `product_image--new` jako nieużywany.
- **Files:** `themes/ayon/templates/catalog/product.tpl`, `themes/ayon/assets/css/custom.scss`
- **Verification:** `document.querySelectorAll('#piece').length === 1` + `pieceParentClass: thumb-container (LI)`
**2. CSS `:has(> #piece)` selector — edge case w kontekście**
- **Found during:** Task 3 CSS + live test.
- **Issue:** `body#product .product_image:has(> #piece) { ... #piece { display: block } }` nie dawał expected override. Compiled CSS selector miał `display: block` ale niekoniecznie match'ował DOM tree (layout-dependent).
- **Fix:** Usunięty `:has()` selector. Reużywamy istniejące stylowanie `.product-images .piece` ze starego SCSS (linia 623) — applikuje się bo `#piece` jest w `.product-images` (z shared partial).
- **Files:** `themes/ayon/assets/css/custom.scss`
- **Verification:** Live test — `#piece` widoczny po kliknieciu popup trigger (display: block z fadeIn).
**3. Inline-script crash: `totalpriceinfospecific` TypeError `null.style`**
- **Found during:** Task 4 live verify (checkpoint).
- **Issue:** Module hook injectuje inline script z funkcją `totalpriceinfospecific` która robi `document.getElementById('totalpriceinfo').style.display = 'block'`. W nowym layoucie `#totalpriceinfo` nie istnieje. Funkcja wywoływana przez `#piece-width change handler` (custom.js:281). Error propaguje przez `trigger('change')` w checkedHandler (custom.js:183) — abortuje przed `is_crop=1`.
- **Fix:** W setTimeout init: `window.totalpriceinfospecific = function() {};` (no-op override). Plus DOM stubs dla pozostałych elementów (`#custom-wallpaper-price`, `#custom-wallpaper-price-label`, `#quantity_wanted*`).
- **Files:** `themes/ayon/assets/js/custom.js`
- **Verification:** Playwright: po init `totalpriceinfospecific.toString()` = no-op, `checkedHandler` dochodzi do `is_crop=1` bez error.
**4. Inline-script crash (wariant #2): `JSON.parse(undefined)` w `totalpriceinfospecific`**
- **Found during:** Task 4 live verify — po stub'owaniu `#totalpriceinfo` error zmienił się na `"undefined" is not valid JSON` przy `$('#product-details').data('product').quantity_discounts`.
- **Issue:** Funkcja ma WIELE DOM dependencies (nie tylko `#totalpriceinfo`). Stubowanie każdego to moving target.
- **Fix:** Override całej funkcji na no-op (zamiast stubowania jej DOM). W nowym layoucie price calc i tak musi przejść inną drogą (do zaplanowania w Plan 02-02).
- **Files:** `themes/ayon/assets/js/custom.js`
- **Verification:** Playwright: `checkedHandler` runs clean, żadnych JSON.parse errorów w console.
**5. Existing ready() fighting my init: checkbox unchecked + values=50**
- **Found during:** Task 4 live verify — po override totalpriceinfospecific state wciąż `checked: false`, `piece-width: 50`.
- **Issue:** `custom.js:209-214` ma `$(document).ready(function() { jQuery('#checkbox-piece').prop('checked', false); $('#piece-width').val(50); $('#piece-height').val(50); checkedHandler(...) })`. Default init dla starego layoutu. Moje setTimeout(600) odpalał się PO tym i wywoływał `checkedHandler($('#checkbox-piece'))` z **unchecked** checkbox → else branch (disabled state).
- **Fix (iteracja 1):** W setTimeout: `$('#checkbox-piece').prop('checked', true); $('#piece-width').val(100); $('#piece-height').val(100);` przed `checkedHandler`. Usunięty po user feedback (piece ma się nie pokazywać na load).
- **Fix (iteracja 2, finalny):** Usunięty `checkedHandler` call w ogóle — piece pojawia się tylko po klikn. trigger'a popupu przez użytkownika. Init tylko wstrzykuje stuby + override funkcji.
- **Files:** `themes/ayon/assets/js/custom.js`
- **Verification:** Playwright: po load `pieceVisible: false` ✓, po `$('.piece-summary')[0].click()``pieceVisible: true, productIsCrop: 1, fancyboxOpen: true` ✓.
### Scope Additions
**1. No-op override `totalpriceinfospecific` + `prod`** (nie w planie) — wymagane dla module hook compat, inaczej piece crash'uje.
**2. DOM stubs injection via JS** (nie w planie) — wymagane jako alternative do niezawodnego deploy'u template'u.
**3. Defensive `.pp_stick_parent` guard** (nie w planie) — wymagane żeby popup się otwierał w nowym layoucie.
### Deferred Items
- **AC-5 regression test starego layoutu** — nie przeprowadzony w tej sesji. Moje zmiany w custom.js są scope'owane pod `.product-variants-data--new` check (jest tylko w nowym layoucie), więc teoretycznie stary nietknięty. Do weryfikacji przed commitowaniem fazy 02 (transition).
- **AC-4 re-init po AJAX variant change** — listener dodany, ale live test zmiany wariantu (click na kafelek → AJAX refresh → `#piece` re-rendered → dragElement re-binds) nie przeprowadzony. Do weryfikacji w kolejnej sesji.
- **Mirror button end-to-end** — handler istnieje w custom.js, w nowym layoucie ma własny przycisk w markup, ale nie testowany klick → `#piece.mirrored` + `#product_is_reflection=1`. Do weryfikacji.
- **Piece-size-view wording** — po kliknieciu popup pokazuje „50x50" (default values) zamiast „Wybierz rozmiar". Cosmetic, opcjonalny future improvement.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Browser cache serwował stary `custom.js` mimo że server miał nowy | Force reload (Ctrl+Shift+Del + clear cache, lub DevTools Network → Disable cache + F5). Playwright: fetch z `?nc=<timestamp>` + dynamic eval init block aby zsynchronizować live state z latest JS. Rekomendacja: w produkcji dodać cache-buster `?v=<version>` do `<script src>` tag'a (Plan 02-02 lub osobno). |
| PrestaShop Smarty cache + FTP sync delay sprawiał że template edits nie dochodziły do browsera | Przeniesienie wszystkich stubs z template do JS (custom.js). Rekomendacja: po każdej zmianie w `.tpl` → Admin PS → Performance → Clear cache. |
| `dispatch`/`trigger` w jQuery forwardsem propagują błąd z nested handler do caller — cały flow abort'uje | Try/catch nie wystarczy (handler sam w sobie rzuca, nie ma sposobu catch poza patchowaniem). Override problematycznej funkcji (no-op) to jedyne czyste rozwiązanie bez modyfikacji nietuchable inline-scriptów. |
## Skill audit
SPECIAL-FLOWS.md nie skonfigurowane. W Plan 02-01 użyte:
- `mcp__plugin_playwright_playwright__*`**krytyczne**. Bez live debug (eval w page context, fetch current script, inspect runtime state) nie dalo by się znaleźć inline-script crashy. Multi-round testing po każdej poprawce.
- Context-mode `ctx_execute` + `ctx_batch_execute` — do eksploracji repo bez zanieczyszczania context window.
**Rekomendacja:** Dodać Playwright MCP jako required skill dla dalszych planów Phase 02 (add-to-cart, empty blocks) — live debug w działającej produkcji jest kluczowy.
## Next Phase Readiness
**Ready:**
- Hidden inputy crop/mirror w formie `#add-to-cart-or-refresh` → gotowe do POST'owania do koszyka przez Plan 02-02.
- Pattern defensywnego setup'u (no-op override + DOM stubs + setTimeout init) reużywalny dla kolejnych napraw module-hook-dependent funkcji.
- Pattern scope'owania CSS pod `.<block>-data--new` do reużycia dla pustych bloków.
- Playwright MCP workflow ustalony — potwierdzone że live debug jest niezbędny.
**Concerns:**
- `totalpriceinfospecific` no-op override wylacza cene calculation w nowym layoucie. Plan 02-02 musi zapewnić alternatywne mechanizm (prawdopodobnie przez PrestaShop `prestashop.on('updatedProduct', ...)` listener ktory czyta `resp.product_prices`).
- Stary layout nie zweryfikowany end-to-end po moich zmianach w custom.js. Ryzyko małe (scope'owane pod marker class), ale do weryfikacji przed commitem phase'a.
- Variant AJAX refresh + re-init piece dragElement — listener dodany ale nie przetestowany. Edge case'y (różne wymiary obrazu między wariantami, state preservation) do przejrzenia.
**Blockers:** None — Plan 02-02 (add-to-cart) może wejść do planowania.
---
*Phase: 02-product-actions-fixes, Plan: 01*
*Completed: 2026-04-23*