Files
newwalls.pl/.paul/phases/02-product-actions-fixes/02-05-PLAN.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

249 lines
11 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: 05
type: execute
wave: 1
depends_on: ["02-03"]
files_modified:
- themes/ayon/assets/js/custom.js
- themes/ayon/templates/catalog/product.tpl
autonomous: false
delegation: off
---
<objective>
## Goal
Wybrana struktura materiału (`<select id="group_5" name="group[5]">` — atrybut „Tekstura materiału") **zapisuje się do koszyka** w nowym layoucie. Obecnie select jest poza formą `#add-to-cart-or-refresh` (w `.product-bar-box`), wiec `$form.serialize()` go nie łapie → POST payload nie zawiera `group[5]` → PS zapisuje wrong attribute combination.
## Purpose
Phase 02 complete ale 02-03 UNIFY pominął że niektóre PS attribute group inputs są **poza formą** (identyczny pattern jak button+qty z Plan 02-02). Kolor (`group[4]`) działa bo radios są w formie. Struktura (`group[5]`) nie — select jest w product-bar, poza formą. Bug production-blocking dla milestone (klient kupuje złą strukturę).
## Output
- Zmiana w POST payload builder: enumeruj WSZYSTKIE `[name^="group"]` poza `#add-to-cart-or-refresh` i dołącz do payload.
- Zmiany w custom.js + inline mirror w product.tpl (identyczne, identity semantics).
- Zero zmian markupie templatu (nie przesuwamy selecta do formy — Bootstrap grid constraint jak przy qty/button).
- Summary: `.paul/phases/02-product-actions-fixes/02-05-SUMMARY.md`.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Prior Work (direct dependency)
@.paul/phases/02-product-actions-fixes/02-03-SUMMARY.md
# Plan 02-03 ustalił pattern: $form.serialize() + dodatkowe pola (qty, sq fields)
# encodeURIComponent'owane w payload. Ten plan rozszerza payload o group[N]
# inputy spoza formy.
## Source Files
@themes/ayon/assets/js/custom.js
# Line ~1083: payload construction w add-to-cart handler:
# var payload = $form.serialize() + '&qty=' + ... + '&' + sqFields + ...
# Tutaj wstrzyknąć enumerację external group inputs.
@themes/ayon/templates/catalog/product.tpl
# Line ~853: inline mirror ma identyczny payload builder (jQuery).
</context>
<skills>
## Required Skills
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| Playwright MCP (`mcp__plugin_playwright_playwright__*`) | required | Task 3 live verification + payload capture | ✓ |
**BLOCKING:** Playwright MCP wymagany w Task 3 — inspect POST payload + cart contents.
</skills>
<acceptance_criteria>
## AC-1: `group[5]` (struktura) w POST payload
```gherkin
Given user na stronie produktu w NEW layoucie (IP 89.69.31.86)
And select `#group_5` ma value "10" (inna struktura niż default value "9")
And piece configured (50×50, checkbox-piece checked)
When user klika "Dodaj do koszyka"
Then POST /pl/koszyk zawiera `group[5]=10` w body
And (wszystkie inne `group[N]` spoza formy tez są w body future-proof dla kolejnych attribute groups)
```
## AC-2: Cart wyświetla wybraną strukturę
```gherkin
Given user zmieni select `#group_5` na strukturę "X" (nie default)
And doda produkt do koszyka
When blockcart modal się otworzy (Plan 02-03)
Then modal (lub preview .blockcart content) pokazuje nazwę struktury "X"
Or (jeśli modal nie pokazuje atrybutów) cart page `/pl/koszyk` pokazuje strukturę "X"
```
## AC-3: Kolor (`group[4]`) nadal działa
```gherkin
Given user nie zmieni nic, zaakceptuje default color + default structure
When POST firuje
Then payload zawiera zarowno `group[4]=5` (default color) jak i `group[5]=9` (default structure)
And cart pokazuje correct combination
```
## AC-4: Zero regression OLD layout
```gherkin
Given strona produktu poza IP 89.69.31.86 (OLD layout)
When user klika "Dodaj do koszyka"
Then PS core handler działa jak dotychczas (zero zmian w flow OLD)
And nie ma JS errors w console
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Inject external group[N] inputs do POST payload</name>
<files>themes/ayon/assets/js/custom.js, themes/ayon/templates/catalog/product.tpl</files>
<action>
W custom.js (add-to-cart handler, obecna linia gdzie payload jest budowany):
Znajdź linię:
```js
var payload = $form.serialize() + '&qty=' + encodeURIComponent(qty) + '&' + sqFields + '&add=1&action=update';
```
Dodaj PRZED tą linią (lub nad budowaniem payload):
```js
// Plan 02-05: group[N] inputy spoza formy (np. #group_5 "Tekstura materiału"
// w .product-bar-box). $form.serialize() ich nie łapie. Enumeruj i dołącz.
var externalGroups = '';
$('[name^="group["]').each(function() {
var $el = $(this);
// Skip te ktore juz sa w formie (bo $form.serialize() je ma)
if ($el.closest('#add-to-cart-or-refresh').length) return;
// Dla radio/checkbox: uwzglednij tylko checked
if (($el.attr('type') === 'radio' || $el.attr('type') === 'checkbox') && !$el.prop('checked')) return;
var n = $el.attr('name');
var v = $el.val();
if (v === undefined || v === null || v === '') return;
externalGroups += '&' + encodeURIComponent(n) + '=' + encodeURIComponent(v);
});
```
Potem zmień payload line na:
```js
var payload = $form.serialize() + '&qty=' + encodeURIComponent(qty) + externalGroups + '&' + sqFields + '&add=1&action=update';
```
Dopisz IDENTYCZNY blok w product.tpl inline mirror (używając `jQuery` zamiast `$`).
**Unikaj:**
- Przesuwania `<select id="group_5">` do formy — Bootstrap grid constraint (identyczna przyczyna jak w Plan 02-02 dla button+qty).
- Double-inclusion `group[N]` jesli jest zarazem w formie i outside (defensive `closest('#add-to-cart-or-refresh').length` check).
- Nadpisywania innych non-group pol spoza formy — filter strict `[name^="group["]`.
- Modyfikacji Plan 02-03 sq fields injection (activation independent).
</action>
<verify>
Node syntax: `node -c themes/ayon/assets/js/custom.js`.
</verify>
<done>AC-1 (payload zawiera group[5]) oraz AC-3 (group[4] nadal obecny) satisfied po Task 3 live verify.</done>
</task>
<task type="auto">
<name>Task 2: FTP deploy obu plików</name>
<files>themes/ayon/assets/js/custom.js, themes/ayon/templates/catalog/product.tpl</files>
<action>
```bash
curl -s -T "themes/ayon/assets/js/custom.js" "ftp://projectpro:i6B.b5P%7Bd6@newwalls.pl/public_html/themes/ayon/assets/js/custom.js" -w "js:%{http_code}\n"
curl -s -T "themes/ayon/templates/catalog/product.tpl" "ftp://projectpro:i6B.b5P%7Bd6@newwalls.pl/public_html/themes/ayon/templates/catalog/product.tpl" -w "tpl:%{http_code}\n"
```
Expected: `js:226` + `tpl:226`.
Post-deploy fetch + grep `externalGroups` na remote custom.js + product page HTML (confirm).
</action>
<verify>
Via Playwright `fetch('/themes/ayon/assets/js/custom.js', {cache:'no-store'})` zawiera `externalGroups` string.
</verify>
<done>Deploy confirmed, Task 3 gate available.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Live verification via Playwright — wszystkie 4 AC</name>
<what-built>
POST payload builder rozszerzony o external `[name^="group"]` inputs spoza formy. Efekt: wybrana struktura materiału (`group[5]`) trafia do koszyka.
</what-built>
<how-to-verify>
**Setup Playwright network capture:**
1. `browser_navigate https://newwalls.pl/pl/prestige/294-4181-rain-of-flowers.html?_new=1`
2. Wait 5s (interval initial render Plan 02-04 + bindings).
3. Change `#group_5` select value do non-default (eg. "10" lub "11"):
```js
const sel = document.getElementById('group_5');
sel.value = '10'; // lub inna available option
sel.dispatchEvent(new Event('change', { bubbles: true }));
```
4. Wymusic piece config: `document.getElementById('checkbox-piece').click()` (jesli nie checked).
5. Dispatchowac piece-width/height input eventy (50×50 baseline).
**AC-1: POST payload capture:**
6. `browser_network_requests` przed kliknieciem — baseline.
7. Klik `.add-to-cart` button.
8. Wait 2s na POST completion.
9. `browser_network_requests` — znajdz POST do `/pl/koszyk` (lub current URL), pobrac requestBody, grep `group[5]=10`.
**Expected:** payload contains `group%5B5%5D=10` (URL encoded).
**AC-2: Cart display:**
10. Po POST, sprawdz `.blockcart` lub navigate do `/pl/koszyk`, czytac produkty — wybrana struktura powinna byc visible w attribute combination.
**AC-3: Default case:**
11. Reload, zostaw defaults (`group[5]=9`, `group[4]=5`).
12. Add to cart, sprawdz payload zawiera `group%5B4%5D=5` AND `group%5B5%5D=9`.
**AC-4: OLD layout regression:**
13. Temporary flip IP gate `'89.69.31.86'` → `'255.255.255.255'` w product.tpl via Edit + FTP.
14. Navigate, add to cart → PS core flow, zero JS errors, cart updates.
15. RESTORE IP gate.
Jesli ktores AC nie spelnione — opisz specific failure (payload field missing, cart wrong structure, etc.).
</how-to-verify>
<resume-signal>Type "approved" LUB describe AC failure.</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Plan 02-03 add-to-cart handler core logic (piece validation, sync is_crop, sqFields build, modal fetch) — rozszerzamy tylko payload line.
- Markup template product.tpl — nie przesuwamy `<select id="group_5">` do formy (Bootstrap grid risk, identyczny powod jak przy button+qty w Plan 02-02).
- Shared `_partials/*.tpl` — tylko custom.js + inline mirror w product.tpl.
- `modules/squaremeter/*` — zero edit.
- OLD layout `{if REMOTE_ADDR != '89.69.31.86'}` block — niedotknięty (AC-4).
- Plan 02-04 price label logic — niezwiązany z tym fixem.
## SCOPE LIMITS
- Ten plan NIE rebuilduje attribute selector UI — tylko POST payload coverage.
- Ten plan NIE obsluguje edge case'ow (np. disabled options, `group[N]` w modal/hidden tab) poza tym ze filtruje radio/checkbox przez `:checked`.
- Ten plan NIE adresuje potencjalnego refresh variant flow gdy user zmienia `group[5]` (Plan 01-01 handler on `group[4]` change może lub może nie pokrywać `group[5]` — audit defer).
- Brak nowych dependencies.
</boundaries>
<verification>
Przed UNIFY:
- [ ] AC-1: Playwright captures POST body zawierający `group%5B5%5D=<selected>`.
- [ ] AC-2: Cart pokazuje wybraną strukturę.
- [ ] AC-3: Default case — `group[4]=5` AND `group[5]=9` oba w payload.
- [ ] AC-4: OLD layout test pass, zero JS errors.
- [ ] Git diff clean: zero zmian poza custom.js + product.tpl.
- [ ] Kod identyczny w obu miejscach.
- [ ] FTP deploy 226/226.
</verification>
<success_criteria>
- Wszystkie 4 AC pass w live Playwright test.
- Zero regresji: kolor variant, customization, modal, price label (Plan 01-01/02-02/02-03/02-04) nietknięte.
- SUMMARY.md dokumentuje pattern "enumerate external group inputs for PS attribute groups poza formą".
</success_criteria>
<output>
Po zakończeniu: `.paul/phases/02-product-actions-fixes/02-05-SUMMARY.md`
</output>