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>
144 lines
12 KiB
Markdown
144 lines
12 KiB
Markdown
---
|
|
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).
|