--- phase: 02-product-actions-fixes plan: 04 subsystem: ui tags: [prestashop, jquery, smarty, piece-configurator, live-price, new-layout] requires: - phase: 02-product-actions-fixes provides: [piece-configurator (#piece-width/#piece-height inputs, 02-01), add-to-cart handler + payload builder z basePrice×area formula (02-03)] provides: - live cena-total labelka obok "Dodaj do koszyka" (.p02p04-total-price) w new layoucie - separate __p02p04Bound guard (niezalezny od __p02p02Bound) - deferred prestashop.on registration pattern (poll until bundle loaded) - interval-based initial render (survives late DOM stub injection + squaremeter overwrite) affects: [02-05 (cache-buster — pozwoli wycofac inline mirror Plan 02-04 tez), przyszle UI labels w NEW layoucie] tech-stack: added: [] patterns: - "Poll-retry rejestracji prestashop.on handlers — bundle loads after inline script parse" - "Separate idempotency guard per Plan (nie współdzielone __pNNBound) dla stale-cache safety" - "Synchronous IIFE init zamiast $(document).ready w kontekscie inline Smarty block" - "setInterval-based reconciliation (10×500ms) gdy external code overwritea DOM po initial render" key-files: created: [] modified: - themes/ayon/assets/js/custom.js # +~55 lines Plan 02-04 block - themes/ayon/templates/catalog/product.tpl # +~50 lines inline mirror IIFE key-decisions: - "Scope pivot mid-implementacja: label obok buttona zamiast nadpisywania .current-price (user request)" - "Separate __p02p04Bound guard — chroni przed stale-cache custom.js blokującym rejestracje w inline mirror" - "Synchronous __p02p04TryInitial() zamiast jQuery(document).ready — ready w inline Smarty block nie firował" - "setInterval 10×500ms (5s okno) zamiast single retry — squaremeter init overwrituje .current-price" - "Poll-retry dla prestashop.on — prestashop bundle loads after inline script parse" - "Inline styling zamiast SCSS edit — unika dependency na user watcher + build" patterns-established: - "Pattern: poll-retry rejestracji handlerow zaleznych od late-loading globals (prestashop, itp.)" - "Pattern: separate IIFE per Plan w inline mirror — kazdy plan ma own guard, nie wspoldzielony" - "Pattern: recurring interval reconciliation gdy istnieje late DOM override source" duration: ~45min (incl. diagnosis + scope pivot) started: 2026-04-24T22:10:00Z completed: 2026-04-24T22:30:00Z --- # Phase 02 Plan 02-04: Live cena per-sqm — summary **Labelka `.p02p04-total-price` obok przycisku "Dodaj do koszyka" w NEW layoucie aktualizuje się w czasie rzeczywistym na zmiane piece-width/piece-height i variant AJAX refresh; górna cena "Od XXX zł / m²" pozostaje statyczna.** ## Performance | Metric | Value | |--------|-------| | Duration | ~45 min | | Started | 2026-04-24T22:10Z | | Completed | 2026-04-24T22:30Z | | Tasks | 3/3 completed | | Files modified | 2 | ## Acceptance Criteria Results | Criterion | Status | Notes | |-----------|--------|-------| | AC-1: Initial price render on load | **PASS** (scope-pivoted) | Label "59,75 zł" (50×50×239); upper `.current-price` pozostaje "Od 239,00 zł / m2" statycznie. Wymagany interval retry 10×500ms (DOM stubs injected late + squaremeter overwrite). | | AC-2: Price updates on width change | **PASS** | Native `input` event na `#piece-width` → label updates ≤350ms, zero HTTP. Weryfikacja: 150×50 → 179,25 zł. | | AC-3: Price updates on height change | **PASS** | `#piece-height` → label updates. Weryfikacja: 150×100 → 358,50 zł. | | AC-4: Price re-renders after variant AJAX refresh | **PASS** | `prestashop.emit('updatedProduct')` → handler zarejestrowany przez poll-retry → recalc 50ms delay → label update. Weryfikacja: meta=500 → "125,00 zł" (vs 239 → "59,75 zł"). | | AC-5: Zero regression OLD layout | **PASS** (code-path) | Guard `.product-variants-data--new` w recalc = early return w OLD. Inline mirror tylko w `{if REMOTE_ADDR=='89.69.31.86'}` bloku — nie renderuje dla OLD. Zero DOM mutacji w OLD. Bez live IP-flip testu (code-path analysis wystarczający dla MVP). | ## Accomplishments - Live total-price labelka **obok przycisku "Dodaj do koszyka"** — natychmiastowa widoczność kwoty przy konfiguracji rozmiaru, klient widzi konkretny koszt przed kliknięciem buttona. - **Upper price "Od 239,00 zł / m²" pozostaje statyczna** — zachowana rola info-labela (per user pivot mid-task). - **Reaktywność na 3 kanały:** (a) user typing → native input events, (b) piece popup dim changes → delegated handlers, (c) variant AJAX refresh → prestashop.on handler z deferred rejestracja. - **Zero HTTP per recalc** — pure JS × basePrice × area_m² (formula z Plan 02-03, single source of truth). ## Files Created/Modified | File | Change | Purpose | |------|--------|---------| | `themes/ayon/assets/js/custom.js` | Modified (+~55 lines) | Plan 02-04 block: `__p02p04EnsureLabel`, `__p02p04RecalcPrice`, delegated input/change bindings, synchronous interval init, poll-retry prestashop.on | | `themes/ayon/templates/catalog/product.tpl` | Modified (+~50 lines) | Inline mirror IIFE w `{block name='content'}` (jQuery zamiast $) — cache-safety dopóki 02-05 systemowego cache-bustera nie ma | ## Decisions Made | Decision | Rationale | Impact | |----------|-----------|--------| | Scope pivot: label obok button zamiast modyfikacji `.current-price` | User request mid-task: "właściwą cenę jako napis obok Dodaj do koszyka. Ta cena u góry Od XXX zł niech będzie stała." | Gorna cena zostaje static info ("Od NNN zł / m²"), konkretna suma eksponowana bliżej buttona — lepszy UX konwersji. | | Separate `__p02p04Bound` guard (nie shared `__p02p02Bound`) | Gdy browser cache'uje stare custom.js (bez P04 kodu) + fresh product.tpl → shared guard by zablokowal inline P04 rejestracje | Stale-cache safety: inline P04 zawsze rejestruje, niezaleznie od wersji cached custom.js | | `window.__p02p04TryInitial()` synchronicznie zamiast `jQuery(document).ready(...)` | jQuery ready w kontekscie inline script wewnatrz `{block name='content'}` Smarty nie firował konsekwentnie (recalc function defined, ale ready callback nigdy nie wywolany) | Initial render dziala reliably; interval handler sam pokrywa DOM-not-ready case | | setInterval 10×500ms (5s window) zamiast single retry | Squaremeter `totalpriceinfospecific` init overwrituje `.current-price` PO naszym pierwszym recalc; + piece dim stubs moga byc late-injected | Robustne pokrycie late-override + late-inject. Po 5s user i tak ma pelna reaktywnosc na dimension events. | | Poll-retry dla `prestashop.on('updatedProduct')` rejestracji | W inline script runtime, `window.prestashop` moze jeszcze nie istniec (bundle loads after inline parse) | AC-4 dziala niezawodnie: handler rejestruje sie gdy tylko prestashop staje sie available | | Inline `style=""` na labelce zamiast SCSS edit | User watcher SCSS build zewnetrzny; inline style unika dependency na build step | MVP widocznosc (16px margin, 1.25rem font-weight:700); pozniejsze plan moze przeniesc do SCSS | ## Deviations from Plan ### Summary | Type | Count | Impact | |------|-------|--------| | Auto-fixed (scope pivot) | 1 | user request — zmienilo target elementu recalc | | Auto-fixed (technical) | 4 | niezbedne fixy ujawnione w live debug Playwright | | Deferred | 1 | AC-5 live IP-flip test (code-path analysis sufficient dla scope) | **Total impact:** Scope pivot zmienil target recalc (upper price → new label). 4 technical deviations byly necessary fixes ujawnione w live debug — nie scope creep. Plan 02-04 dziala w production (live verified). ### Auto-fixed Issues **1. [Scope pivot] Target recalc: label zamiast `.current-price`** - **Found during:** Task 3 checkpoint (user polecenie mid-verification) - **Issue:** Pierwotny plan kazal pisac do `.product-prices .current-price` (nadpisywac "Od 239 zł / m²"). User pivot: gorna cena ma zostac statyczna. - **Fix:** Nowa funkcja `__p02p04EnsureLabel()` tworzy `` przed `.product-quantity .add`. Recalc pisze tylko do labelki. `.current-price` nietknieta. - **Files:** custom.js, product.tpl (inline mirror) - **Verification:** Playwright — upper stays "Od 239,00 zł / m2" across wszystkich AC, label dynamicznie pokazuje kalkulacje. **2. [Guard isolation] Separate `__p02p04Bound` zamiast shared `__p02p02Bound`** - **Found during:** Task 1 pre-implementation code review - **Issue:** Plan sugerowal dodac P04 kod "wewnatrz istniejącego IIFE po __p02p02Bound guard". To znaczylo ze stale-cache custom.js (ktore ma __p02p02Bound=true ale bez P04) by zablokowal P04 rejestracje w inline mirror (kiedy inline sprawdza shared guard i skipuje caly IIFE). - **Fix:** Dodany osobny IIFE z `__p02p04Bound` guard w product.tpl. Custom.js rowniez uzywa `__p02p04Bound` (nie shared). - **Files:** custom.js, product.tpl - **Verification:** Plan deploy + Playwright — inline mirror rejestruje sie niezaleznie od custom.js stanu cache. **3. [Init timing] `$(document).ready` → synchronous call** - **Found during:** Task 3 live debug — timeline polling pokazal ZERO price transitions po 6s mimo ze recalc function defined. - **Issue:** `jQuery(document).ready(window.__p02p04TryInitial)` w kontekscie inline script wewnatrz `{block name='content'}` Smarty nie firowal (hypothesis: jQuery ready w inline Smarty block ma timing/scope issue). - **Fix:** Zmienione na `window.__p02p04TryInitial()` synchronicznie. Interval sam pokrywa DOM-not-ready case (recalc early-return'uje jesli brak inputow). - **Files:** custom.js, product.tpl - **Verification:** Timeline polling: initial render fires within ~500ms consistently. **4. [Late override] Retry loop 4×350ms → setInterval 10×500ms** - **Found during:** Task 3 — pierwszy retry pattern (stop-on-success) miał data-calc=null po 6s. - **Issue:** Squaremeter `totalpriceinfospecific` init (OR inna funkcja) overwrituje `.current-price` PO naszym pierwszym recalc. Stop-on-success pattern przestawal po 1 attempt (data-calc set), potem external overwrite, no more retry. - **Fix:** Pure interval 10×500ms = 5s okno. Po expire user i tak ma reaktywnosc na input events. - **Files:** custom.js, product.tpl - **Verification:** Playwright — label stabilizuje sie poprawnie, manual test setInterval calls kazdy poprawny. **5. [prestashop timing] prestashop.on → poll-retry registration** - **Found during:** Task 3 — AC-4 verify pokazal ze `prestashop.emit('updatedProduct')` nie triggerowal recalc. - **Issue:** Inline script sprawdzal `if (window.prestashop && ...)` synchronicznie przy parse. `window.prestashop` jeszcze nie istnial — prestashop bundle laduje po inline script. Warunek fail, handler nigdy nie rejestrowany. - **Fix:** `(function bindUpdatedProduct() { if (...) prestashop.on(...); else setTimeout(bindUpdatedProduct, 200); })()` — polls az prestashop dostepny. - **Files:** custom.js, product.tpl - **Verification:** Playwright — prestashop.emit triggeruje label re-render z fresh meta (100, 500 test values). ### Deferred Items - **AC-5 live IP-flip test** — zamiast live test (flip IP gate w product.tpl, FTP upload, navigate, restore) wybrana code-path analysis. Guard `.product-variants-data--new` w recalc + inline mirror tylko w `{if REMOTE_ADDR...}` bloku daje strong correctness argument. Pozniejszy /paul:verify moze dodac live test w calym milestone. ## Issues Encountered | Issue | Resolution | |-------|------------| | jQuery `.trigger('input')` nie firuje delegated handler w test | Obchodzenie testowe przez native `.dispatchEvent(new Event('input', {bubbles:true}))` — reflektuje real user interaction correctly. | | Browser cache serwuje old custom.js mimo FTP 226 | `browser_close` + fresh navigate between iterations. Systemowy cache-buster (Plan 02-05) rozwiaze long-term. | | Manual dispatch przez native event różni się od jQuery trigger (AC verify methodology) | Konkluzja: native dispatch == real user typing == handler fires. jQuery trigger artifact testowy. UX behavior confirmed. | ## Next Phase Readiness **Ready:** - Milestone v0.1 **COMPLETE-READY** — Phase 2 Plan 02-04 zamyka core UX (live price visibility). Pozostaje Plan 02-05 (systemowy cache-buster) jako nice-to-have czysty cleanup inline mirror. - Plan 02-05 ma pattern z Plan 02-04 bindUpdatedProduct() do reużycia (poll-retry registration dla late globals). **Concerns:** - Inline mirror w product.tpl nadal obecny — Plan 02-05 planowal cache-buster pozwalajacy wycofac inline mirror. Jesli milestone ship'uje bez 02-05, inline mirror zostaje (działa poprawnie, ale ~55 extra lines w kazdym product page response). - `setInterval 10×500ms` na każdym product page load — trywialny koszt CPU, ale idiomatycznie lepiej by było wire'owac do konkretnego "squaremeter init complete" eventu gdyby istnial. - Inline style na labelce — pozniejszy plan moze zsynchronizowac z SCSS (theme spacing/typography tokens). **Blockers:** - None. --- *Phase: 02-product-actions-fixes, Plan: 04* *Completed: 2026-04-24*