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>
12 KiB
12 KiB
phase: 02-product-actions-fixes
plan: 02
subsystem: ui+backend-integration
tags: [prestashop, smarty, jquery, ajax, add-to-cart, squaremeter, customization, cache]
status: PARTIAL (core flow działa, ale end-to-end UX wymaga Plan 02-03 — customization nie zapisuje się i modal nie pojawia)
requires:
- phase: 02-product-actions-fixes
provides:
- Plan 02-01 form
#add-to-cart-or-refresh z hidden inputs (crop/mirror) — wykorzystane jako baseline POST payload
- Plan 02-01 marker class .product-variants-data--new — użyty do scope'owania handler'a
provides:
- Capture-phase click handler na [data-button-action=add-to-cart] w custom.js (+inline mirror w product.tpl jako cache-buster)
- Blokuje PS core handler (ktory crash'owal bo button poza forma) przez stopImmediatePropagation na capture phase
- Manualny POST do form.action z form.serialize() + qty + add=1&action=update
- Fancybox-blocker port (walidacja ze piece wybrany przed submit)
- Sync is_crop/crop_width/crop_height przed POST (obejście crash'u checkedHandler)
- prestashop.emit('updatedCart') + blockcart refresh fetch
- Loading spinner + success flash animation
affects:
- Plan 02-03 (cena + customization + modal) — BLOKER dla production readiness:
- Customization nie zapisuje się bo squaremeter::hookActionObjectCartUpdateBefore wymaga discretion=on + squaremeter fields (dim, qty, qty_alth, product_total_price_calc, extrafeevalue, wastevalue, calculated_total, etc.)
- Plan 02-01 override totalpriceinfospecific wyłączył synchronizację tych pól w nowym layoucie
- Success modal (po add-to-cart) wymaga osobnego POST do /module/ps_shoppingcart/ajax?action=add-to-cart → renderowany przez Ps_Shoppingcart::renderModal()
- W koszyku brakuje "Szczegóły" button bo cart.id_customization = 0
tech-stack:
added: []
patterns:
- "Capture-phase native addEventListener z useCapture=true — jedyna metoda blokowania PS core delegated bubble handlers w jQuery.on()"
- "Inline script w product.tpl jako cache-buster dla handler'a, guard'owany window.__<flag>Bound — immune na browser cache statycznych assetow"
- "Stopniowa diagnoza structure-first (DOM traversal → event phases → POST payload) w Playwright przed zmianami kodu"
key-files:
created:
- .paul/phases/02-product-actions-fixes/02-02-PLAN.md
- .paul/phases/02-product-actions-fixes/02-02-SUMMARY.md
modified:
- themes/ayon/assets/js/custom.js (wrapped Plan 02-02 block w if (!window.__p02p02Bound) { ... } guard, ~115 linii)
- themes/ayon/assets/css/custom.scss (+loading spinner + added-flash, ~42 linie)
- themes/ayon/templates/catalog/product.tpl (+inline handler mirror przed {/if} new-layout, ~95 linii) — cache-buster
key-decisions:
- "Task 1 diagnoza via Playwright → S3 (własny AJAX submit) — struktura DOM: button+qty są poza forma, PS core closest('form') zwraca 0 elementów, POST nigdy nie wychodzi"
- "Capture-phase native addEventListener zamiast jQuery .on() — eliminuje double-POST (PS core registered first, jQuery stopPropagation nie mogło cofnąć wcześniejszej rejestracji)"
- "Manual sync is_crop/crop_width/crop_height w handlerze — obejście crash'u checkedHandler (totalpriceinfospecific override z 02-01 nie działa w produkcji)"
- "Inline script w product.tpl + idempotency guard — cache-buster dla <script src=custom.js> bez modyfikacji gdzieśtam rejestracji"
- "UNIFY jako PARTIAL — szczerze raportować że customization + modal wymagają Plan 02-03 z powodu zależności od squaremeter flow (ktory 02-01 wyłączyło no-op override'em)"
patterns-established:
- "Capture-phase handler na window events: document.addEventListener('click', fn, true) — dla blokowania globalnych delegated handlerów PS core bez modyfikacji core.js"
- "Idempotency guard flag window.__<planId>Bound — pozwala duplikować handler (custom.js + inline template) bez double-execution"
- "HTML response inline <script> jako cache-buster dla statycznych assetów — immune na browser cache + PS 1.7 Smarty cache invaliduje sie na kolejnej pełnej pre-renderyzacji"
duration: ~4h (Task 1 diagnoza 30min, Task 2 iteracje 2h, Task 3 CSS 15min, debugging cache+customization discovery 1.5h)
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: POST dociera do /koszyk z pełnym payloadem | Pass | Playwright verified: POST z is_crop=1, crop_width/height, qty, add=1, action=update. Response success:true. |
| AC-2: Fancybox-blocker bez piece config | Pass (code review) | Straight-line if-return logic w handler'ze. Live test session pollution blokowała izolowany test, ale logika prosta i sprawdzona w kodzie. |
| AC-3: Cart widget counter się odświeża | Pass | Cart-products-count aktualizowany z "000" na "1" po POST. Mechanizm: prestashop.emit('updatedCart') + manual blockcart fetch. |
| AC-4: Error response → czytelny komunikat | Pass (code review) | Handler parse'uje resp.errors (array lub object), pokazuje fancybox. Symmetric do success path. Nie live-tested bo wymagałoby symulacji error response. |
| AC-5: Zero regresji starego layoutu | Pass (by design) | Early return if (!document.querySelector('.product-variants-data--new')) return — stary layout nie wywołuje preventDefault/stop, PS core handler wykonuje się normalnie. |
## Accomplishments
- Diagnoza struktury DOM — ustalenie że forma i button są w rozdzielnych kontenerach Bootstrap (form w sidebar .col-md-6, button w szerokim .product-bar). PS core handler nie może dotrzeć do formy.
- Capture-phase breakthrough — pierwsza iteracja (jQuery .on()) powodowała double-POST. Refactor na native addEventListener z useCapture=true w pełni blokuje PS core. Verified nativeClicks:1, delegated:0.
- Cache immunity — identyfikacja browser cache jako root cause "niby zadziałało ale u użytkownika nie". Rozwiązanie: inline script w template + idempotency guard pozwala na parallel deploys (custom.js + inline) bez konfliktu.
- Głęboka diagnoza customization flow — rozpoznanie że PS 1.7 + squaremeter używa dwóch dróg: cart controller override + hookActionObjectCartUpdateBefore gated przez discretion=on. Success modal przez /module/ps_shoppingcart/ajax?action=add-to-cart. Oba wymagają pól których Plan 02-01 nie dostarcza.
## Deviations from Plan
### Auto-fixed Issues
1. Double POST — PS core + nasz handler oba wysyłają
- Found during: Task 2 first live test — posts: [2 entries], oba identyczne.
- Issue: jQuery $(document).on('click', ..., ) registered AFTER PS core → stopImmediatePropagation w naszym handlerze nie blokowało PS core (registered earlier).
- Fix: Refactor z jQuery .on() na native document.addEventListener('click', fn, true) (capture phase). Capture fires PRZED bubble, blokuje PS core całkowicie. Verified nativeClicks:1, delegated:0.
- Files: themes/ayon/assets/js/custom.js
2. Cart widget counter nie odświeżał się po emit('updatedCart')
- Found during: Task 2 live test — response success ale .cart-products-count pokazywał "000".
- Issue: Natural PS blockcart listener nie jest zarejestrowany w nowym layoucie / nie reaguje.
- Fix: Dodany manual $.get('/koszyk', {action:'refresh', ajax:1}) po successful POST → defensive replace .blockcart z response. W real teście counter zaktualizował się ("000" → "1") — prawdopodobnie natural listener też odpalił + nasz backup.
- Files: themes/ayon/assets/js/custom.js
3. Browser cache serwował starą wersję custom.js
- Found during: User raport po FTP deploy — "klikam i nic się nie dzieje". Diagnoza Playwright: perfEntries[0].decodedBodySize = 30.6 KB vs server fetch.size = 41.3 KB, transferSize: 0 (from cache).
- Issue: <script src="custom.js"> rejestrowany bez query param (version). Browser cache serwuje stale.
- Fix (częściowy): Dodany inline mirror handler w product.tpl new-layout branch. HTML response zawsze zawiera świeży kod. Idempotency guard window.__p02p02Bound zapobiega double-register jeśli cached custom.js też zawiera handler.
- Pozostało: Systemowy cache-buster dla custom.js <script> tag — defer do Plan 02-03 razem z innymi cross-cutting concerns.
- Files: themes/ayon/templates/catalog/product.tpl
### Scope Additions
- Inline script cache-buster w product.tpl — nie planowane w Task 2, ale niezbędne po odkryciu że browser cache blokuje deploy.
- Głęboka diagnoza squaremeter flow — rozpoznanie że customization zapis wymaga discretion=on + dimension fields. Rezultat: identyfikacja zakresu Plan 02-03.
### Deferred Items → Plan 02-03
- Customization save w koszyku — crop/mirror data (+ dimension gdy zaimplementowane) musi utworzyć customization z in_cart=0 w DB żeby user widział "Szczegóły" button w cart. Wymaga POST z discretion=on + squaremeter fields.
- Success modal po add-to-cart — POST do /module/ps_shoppingcart/ajax?action=add-to-cart → render modal z "Kontynuuj zakupy / Przejdź do koszyka".
- Cena per-sqm kalkulacja w nowym layoucie — wymaga przywrócenia squaremeter dimension flow (zamiast no-op override z Plan 02-01). Powiązane z customization bo product_total_price_calc jest jednym z pól.
- Systemowy cache-buster custom.js — zamiast inline duplicate, dodać ?v={mtime} do rejestracji script tag'a (znaleźć miejsce rejestracji: theme.yml, FrontController override, lub PS hook).
- Plan 02-01 override totalpriceinfospecific live-debug — w produkcji override nie jest aktywny (ORYGINAL function crashes na klik piece-summary). Root cause: setTimeout(600) timing vs inline ready callbacks. Do naprawy w Plan 02-03 razem z przywróceniem squaremeter flow.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| PS core handler registered before us → jQuery stopPropagation nie blokuje PS core | Capture-phase native addEventListener (useCapture=true) fires PRZED bubble phase PS core — całkowicie go blokuje. |
| Browser cache servuje starą wersję custom.js mimo że server ma nową (FTP sync zakończony, marker "Phase 02 Plan 02-02" obecny server-side) | Inline script mirror w product.tpl + idempotency guard. HTML response serwuje świeży kod z każdym request. |
| Playwright test session state leakage (poprzednie handlery persist'ują mimo navigate) | page.goto() wyglada jak full reload ale cached JS execution state może przetrwać przy hash-only URL changes. Rozwiązanie: bardziej aggressive cache busting przez navigate na URL bez fragmentu. |
| Customization nie zapisuje się mimo że is_crop/crop data są w POST payload | Squaremeter hook hookActionObjectCartUpdateBefore jest gate'owany przez discretion=on — brak tego pola w naszym POST. Delegowane do Plan 02-03. |
| Success modal nie pojawia się | PS core post-success flow oczekuje osobnego response z modal key z endpointa /module/ps_shoppingcart/ajax. Nasz POST idzie tylko do /koszyk. Delegowane do Plan 02-03. |
## Skill audit
Użyte w Plan 02-02:
- Playwright MCP — krytyczne. Bez live debug (event phase tracing, network capture, DOM inspection, iterowanie handlera bez FTP deploy cycle) nie dałoby się znaleźć capture-phase fix'u ani diagnoza customization flow. Wiele iteracji.
- context-mode — ctx_batch_execute/ctx_execute_file do eksploracji PS core (core.js), squaremeter source (override controllers + hooks) bez zanieczyszczania context.
Pattern established: Structure-first diagnosis — zanim jakakolwiek implementacja, sprawdzić DOM (form-button relationship), event flow (capture/bubble), POST payload (what fields). Oszczędza cykle debug.
## Next Phase Readiness
Ready:
- Capture-phase pattern reużywalny dla kolejnych globalnych event overrides (modal close, cart update, etc.)
- Inline template cache-buster pattern reużywalny dla wszystkich JS zmian które wymagają "always fresh" deploy
- Idempotency guard pattern dla duplikowanych handler'ów
Concerns:
- Plan 02-02 feature jest PARTIAL: add-to-cart technicznie działa (produkt w koszyku), ale niezadowalające UX: brak modal potwierdzenia, brak customization details → user nie widzi co zamówił w szczegółach.
- Plan 02-01 override totalpriceinfospecific aktywnie uszkadza squaremeter flow → cascade do cena + customization.
- Real user flow na produkcji nadal wymaga Plan 02-03 do "acceptable" state.
Blockers dla publikacji nowego layoutu: Plan 02-03 (modal + customization + cena) MUST be done przed umożliwieniem layout-u zwykłym użytkownikom (usunięcie IP gate).