Files
newwalls.pl/.paul/phases/02-product-actions-fixes/02-04-SUMMARY.md
Jacek Pyziak ac03f807c1 feat(02-product-actions-fixes): Phase 02 complete — customization, price label, structure fix
Plan 02-03: Customization save + success modal (5/5 AC)
- 26-field squaremeter POST payload (verbose PL dim, qty_alt/qty_alth)
- Chain POST /module/ps_shoppingcart/ajax -> Bootstrap #blockcart-modal
- Critical fix: moved {/block} so inline script actually renders
- __p02p02InFlight re-entrancy guard

Plan 02-04: Live cena per-sqm label obok "Dodaj do koszyka" (5/5 AC)
- .p02p04-total-price label, gorna .current-price static
- Separate __p02p04Bound + setInterval reconciliation
- Poll-retry prestashop.on registration

Plan 02-05: Struktura materialu w POST payload (4/4 AC)
- Enumerate [name^="group["] spoza formy, doklej do payload
- Fix: group_5 select w .product-bar-box nie trafial do koszyka

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:55:05 +02:00

176 lines
13 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: 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 `<span class="p02p04-total-price">` 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*