PanPodloga.pl - PP Inspiracje (Shop The Look) WordPress Plugin
  • PHP 69.5%
  • JavaScript 19.4%
  • CSS 10.6%
  • HTML 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-02 05:20:30 +02:00
00-research feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
_backup_v1005 feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
assets feat(cro): implement 1-field instant auth, mardom decor promo, 48h dismiss safety and selftest checks 43-45 2026-10-02 05:16:10 +02:00
dane feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
docs feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
docs-featured-image feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
docs-new feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
includes fix(selftest): ensure boolean accumulation in chk_mardom_promo_rule 2026-10-02 05:20:30 +02:00
languages feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
tasks feat: implement server-side remarketing & meta conversions api (capi) 2026-10-02 01:09:02 +00:00
templates feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
tests feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
.gitignore feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
CHANGELOG.md feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
deploy_temp.py feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
DOKUMENTACJA.md feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
oferta.html feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
pp-inspirations.php feat: implement server-side remarketing & meta conversions api (capi) 2026-10-02 01:09:02 +00:00
pp-inspirations.php.bak_20260930 feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
pp-inspirations.php.bak_fix_20260929 feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
pp-inspirations.php.bak_rvdiag_20260821_180032 feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
RAPORT-AUDYTU.md feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00
readme.txt fix: update stable tag to 10.0.8 in readme.txt and prevent selftest self-scan false positives 2026-10-02 03:48:34 +02:00
uninstall.php feat: initial commit of pp-inspirations with remarketing architecture and live v10.0.8 parity 2026-10-02 01:56:03 +02:00

=== PP Inspiracje (Shop The Look) ===
Contributors: lukaszpikus
Tags: woocommerce, inspiracje, wizualizacja, ai, hotspots, ugc
Requires at least: 5.8
Tested up to: 6.7
Requires PHP: 7.4
Stable tag: 10.0.8
License: GPLv2 or later

System galerii inspiracji, oznaczania produktów WooCommerce oraz generowania
wizualizacji wnętrz AI z moderowaną galerią treści klientów.

== Description ==

Wtyczka łączy trzy moduły:

1. **Inspiracje (Shop The Look)** — galeria aranżacji z oznaczonymi produktami (hotspoty),
   wykrywanymi automatycznie przez model wizyjny.
2. **Room Visualizer** — klient wgrywa zdjęcie swojego pomieszczenia, a AI podmienia
   podłogę, listwy lub sztukaterię na wybrany produkt ze sklepu.
3. **Galeria klientów** — wizualizacje klientów prezentowane na kartach produktów
   użytych w danej aranżacji, wyłącznie za wyraźną zgodą klienta i po moderacji.

= Zgodność z RODO =

Publikacja treści klientatywnie wymaga zgody udzielanej osobno dla każdej wizualizacji.
Zgoda jest odwoływalna jednym kliknięciem ze skutkiem natychmiastowym, a każde
zdarzenie (udzielenie, wycofanie, decyzja moderatora) trafia do dziennika audytowego.
Wtyczka rejestruje eksporter i mechanizm usuwania danych w narzędziach prywatności
WordPressa. Metadane EXIF/GPS są usuwane ze zdjęć klientów przy wgrywaniu.

= Moderacja =

Żadna treść klienta nie trafia publicznie bez decyzji moderatora. Panel moderacji
znajduje się w menu Inspiracje → Moderacja wizualizacji.

== Installation ==

1. Wgraj katalog wtyczki do `/wp-content/plugins/`.
2. Aktywuj wtyczkę — migracje schematu wykonają się automatycznie.
3. Uzupełnij klucz API w ustawieniach wtyczki.
4. Sprawdź ustawienia bezpieczeństwa w Inspiracje → Bezpieczeństwo i moderacja UGC.

== Changelog ==

= 10.0.8 =
* Wdrożenie pełnego silnika remarketingowego dla Google Ads (GLA feed ID) i Meta Conversions API (CAPI v20+).
* Most telemetrii wizualizatora pokoju AI oraz zaawansowane dopasowanie Enhanced Conversions (SHA-256).
* Integracja z Consent Mode v2 i panelem administracyjnym konfiguracji pikseli.

= 9.6.9 =
* Poprawa stabilności generatora wizualizacji: dodano automatyczne filtrowanie fraz zakazanych z promptów (rendering, photorealistic).
* Zabezpieczenie przetwarzania rastra GD przed błędem load_failed poprzez odczyt z bufora bajtowego imagecreatefromstring.
* Łatki kompatybilności Schema oraz autotestu spójności dla Rank Math SEO.

= 8.9.0 =
* Moduł Photogen AI: Poprawa prezentacji historii wersji z podglądem paska wszystkich miniatur ujęć oraz plakietkami ikon/etykiet typów ujęć.
* Naprawa ekstraktora atrybutów WooCommerce z automatycznym dopasowaniem polskich znaków (wysokość, szerokość, długość, powłoka, materiał, producent, symbol).
* Ujednolicenie taga wersji v8.9.0 oraz dodanie jawnego do_action dla haków czyszczenia batchy.

= 8.8.0 =
* Moduł Photogen AI: Przebudowa menedżera wyboru produktów, wyszukiwarka live AJAX z autokompletacją, dodawanie kategorii produktów WooCommerce oraz wykluczenia.
* Wdrożenie równoległego wykonywania zadań AI (do 5 równolegle, domyślnie 3).
* Precyzyjne raportowanie etapów postępu oraz wyliczanie czasu pozostałego do końca.
* Ujednolicenie tagów wersji oraz czyszczenie wygasłych batchy.

= 3.31.1 =

BIALA STRONA NA "NAZWY I OPISY GMC"
* Zgloszenie: Uncaught Error: Unknown format specifier "\" w
  class-pp-insp-gmc-admin.php linia 402, wywolanie
  sprintf( 'Frazy pobrane dla %1..., nieudanych: %2\$d.', 15, 0 ).
* PRZYCZYNA. Komunikat byl zapisany w APOSTROFACH z ukosnikiem przed dolarem:
  '%1\$d'. W apostrofach PHP nie interpretuje dolara, wiec ukosnik zostaje w
  tekscie i sprintf dostaje specyfikator "\". Ukosnik jest potrzebny WYLACZNIE
  w cudzyslowach, gdzie $d byloby zmienna.
* Render umiera w polowie, wiec znika TAKZE wszystko ponizej: tabela zestawow,
  formularz i przyciski. Wlasciciel widzi sama ramke bledu.
* Objaw pojawia sie tylko na jednej galezi, po powrocie z pobierania fraz, wiec
  przeszedl przez lint, przez wszystkie testy i przez reczne klikanie.

POMIAR CALEJ KLASY WADY
* Przeszukalem wtyczke pod katem tego samego zapisu: 1 wystapienie na 161
  plikow. Naprawa dotyczy jednego miejsca, ale zabezpieczenie dotyczy wszystkich.

NOWY TEST: KAZDY FORMAT printf DA SIE WYKONAC
* tests/formaty-printf.test.php wyciaga kazdy literal stojacy na PIERWSZYM
  miejscu w wywolaniu printf, sprintf, vsprintf, vprintf i wp_sprintf, takze
  ten opakowany w funkcje tlumaczaca, i URUCHAMIA go.
* Zakres pomiaru: 161 plikow PHP, 367 formatow, 0 odrzuconych po naprawie.
* SPRAWDZENIE WZORCEM NIE WYSTARCZA. Pierwsza redakcja brala kazdy tekst z
  procentem i dala 66 trafien, z ktorych 65 nie bylo formatem: SQL z LIKE '%',
  style width:100%, etykiety "Wysokosc rabatu (%)". Wykonanie nie zgaduje,
  ktory znak jest specyfikatorem.
* Test pilnuje takze DRUGIEJ POLOWY tej samej wady: sprintf z mniejsza liczba
  argumentow, niz wymaga format. Taki nie wywala strony, tylko cicho wstawia
  pustke, wiec wlasciciel czyta "nieudanych:" bez liczby i nie ma powodu tego
  zglaszac. Zbadane: 215 wywolan z literalnym formatem, 0 brakow.

CZEGO TEN TEST NIE ROBI
* Pierwsza redakcja liczyla takze balans znacznikow w metodzie renderujacej i
  zglosila rozjazd 5 wobec 6 na POPRAWNYM kodzie: galaz z wczesnym return
  domyka opakowanie strony u siebie, wiec proste zliczanie musi tam pokazac
  nadmiar. Sprawdzenie usuniete. Falszywy alarm w tescie jest gorszy niz brak
  testu, bo uczy ignorowania czerwonego wyniku.

DOWODY MUTACYJNE
  | Mutacja                                | Upadlo asercji |
  |----------------------------------------|----------------|
  | powrot ukosnika w zgloszonej linii     | 3              |
  | usuniecie drugiego argumentu           | 1              |
  | zly specyfikator w INNYM pliku         | 1              |
  | brak argumentu w INNYM pliku           | 1              |

* Dwie ostatnie mutacje celowo w innych plikach: mechanizm ma lapac klase wady
  w calej wtyczce, a nie pilnowac jednej naprawionej linii.

DODATKOWA KONTROLA STRONY GMC
* Sprawdzone, ze zadna metoda wolana na tej stronie nie jest nieistniejaca:
  6 wywolan self:: wobec 14 metod w klasie, 19 wywolan miedzyklasowych do
  pieciu klas SEO. Zero brakow. Blad krytyczny w polowie renderu ukrywa
  nastepne, wiec po naprawie trzeba sprawdzic reszte sciezki, a nie tylko
  zglaszany komunikat.

STAN PO WYDANIU
  | Miara                  | 3.31.0 | 3.31.1 |
  |------------------------|--------|--------|
  | zestawow testow        | 38     | 39     |
  | asercji                | 2 347  | 2 356  |
  | relacji spojnosci      | 43     | 43     |
  | formatow printf w kodzie | brak pomiaru | 367 |
  | bledow lintera PHP     | 0      | 0      |

= 3.31.0 =

WEJSCIE Z PRODUKTU I Z INSPIRACJI SAMO ZAZNACZA POWIERZCHNIE
* Zgloszenie: "logika, w ktorej przechodzimy z produktu albo z inspiracji,
  powinna automatycznie oznaczac dane powierzchnie, bo produkty sa juz
  wybrane". Na zrzucie: dwa wybrane produkty, ZERO zaznaczonych powierzchni,
  wygaszony przycisk "Dalej: Wgraj zdjecie".
* PRZYCZYNA NIE BYLA TAM, GDZIE OBJAW. Wyprowadzanie zakresu z produktow
  ISTNIALO od 3.19.0: State.init czyta powierzchnie kazdego wybranego produktu
  przez Kroki.powierzchniaProduktu(). Nie dzialalo, bo produkty przychodzace z
  karty produktu i z inspiracji NIE MIALY pola powierzchni.
* POMIAR ZRODEL OPISU PRODUKTU

  | Zrodlo                                        | Pol | surface |
  |-----------------------------------------------|-----|---------|
  | pp_rv_search_products, lista                  |  11 | jest    |
  | pp_rv_search_products, pojedynczy             |  10 | jest    |
  | get_products_meta_data (URL, inspiracja, CTA) |   4 | BRAK    |
  | render_inline_creator_for_product (karta)     |   4 | BRAK    |
  | get_inspiration_page_data (CTA inspiracji)    |   4 | BRAK    |
  | podpowiedzi zestawu w ajax                    |   5 | BRAK    |
  | analizator pomieszczenia                      |   5 | BRAK    |
  | galeria spolecznosci w szablonie              |   5 | BRAK    |

* Osiem miejsc skladalo te sama strukture, szesc z nich pomijalo powierzchnie.
  Objaw wygladal na wade przycisku, a przyczyna siedziala w danych sprzed
  dwoch ekranow.

OPIS PRODUKTU MA JEDNEGO AUTORA
* Nowa klasa PP_RV_Produkt_Dane. Kontrakt: id, name, price, thumb, type, slot,
  slot_label, surface, limit_kat, limit_kat_max.
* Wszystkie osiem zrodel wola teraz ja zamiast skladac tablice u siebie.
  Dopisanie nowego pola w osmiu miejscach zawsze konczylo sie dopisaniem go w
  dwoch, a wada wychodzila dopiero u klienta.
* Powierzchnia liczona po stronie serwera z kanonicznej mapy klasyfikatora:

  | Typ produktu                             | Powierzchnia |
  |------------------------------------------|--------------|
  | floor, skirting                          | floor        |
  | wall_panel, wall_moulding, wall_slat     | wall         |
  | ceiling_moulding, rosette                | ceiling      |
  | other i typy nieznane                    | zadna        |

* Typ nieznany NIE zgaduje podlogi. Zaznaczenie zakresu, ktorego klient nie
  wybral, kazaloby mu odznaczac cos, czego sam nie ustawil.

SYNCHRONIZACJA ZAKRESU W JEDNYM MIEJSCU, WOLANA Z TRZECH
* State.zsynchronizujZakres(). Derywacja stala wczesniej wylacznie w init, a
  produkty trafiaja do stanu takze pozniej: z adresu z parametrem product_id
  oraz z przywrocenia sesji po zalogowaniu. Na obu tych sciezkach zakres
  zostawal pusty.
* DOPISUJEMY, NIGDY NIE ODBIERAMY. Klient, ktory sam zaznaczyl sufit bez
  produktu sufitowego, zamierza zmienic kolor albo dobrac produkt za chwile.
* Metoda jest idempotentna: drugie wywolanie nie dopisuje niczego.
* Wejscie z pustymi rekami NADAL widzi wygaszony krok pierwszy. Nie ma z czego
  wyprowadzic zakresu i tak ma byc.

NOWA RELACJA SPOJNOSCI (43)
* "Opis produktu dla wizualizatora ma jednego autora": zadna tablica z nazwa i
  miniatura nie moze powstawac poza klasa kanoniczna. Relacja od razu znalazla
  dwa zrodla, o ktorych nie wiedzialem, a testujac recznie znalazlbym pewnie
  jedno.

TESTY ZMIENIONE, NIE NAPRAWIONE
* kroki.test.js: trzy asercje pytaly, czy plik ajax sklada opis produktu z
  powierzchnia i slotem w dwoch miejscach. Pytaja teraz o kanoniczna klase i o
  to, czy obie sciezki ja wolaja. Wymog jest MOCNIEJSZY niz byl.
* catalog-rules.test.php: liczba wystapien limit_kat w pliku ajax spadla z 3 na
  1, bo opis produktu sie wyprowadzil. Asercje przekierowane na klase
  kanoniczna, z osobnym sprawdzeniem, ze wewnetrzna bramka limitow zostala.
* Relacja R33 "slot i powierzchnia podrozuja razem" liczyla struktury w pliku
  ajax i po scaleniu zglosila zero zbadanych, czyli poprawnie: jej wzorzec
  przestal pasowac do kodu. Przekierowana tam, gdzie opis teraz mieszka.

DOWODY MUTACYJNE
  | Mutacja                                    | Upadlo asercji |
  |--------------------------------------------|----------------|
  | kontrakt bez powierzchni                   | 2              |
  | opis nie niesie powierzchni                | 1 (po poprawie)|
  | powierzchnia zgadywana dla nieznanego typu | 1              |
  | preselekcja wraca do wlasnego opisu        | 1              |
  | start kreatora nie synchronizuje           | 1              |
  | produkt z adresu nie synchronizuje         | 1              |
  | przywrocenie sesji nie synchronizuje       | 1              |
  | synchronizacja odbiera reczny wybor        | 2              |
  | synchronizacja dubluje powierzchnie        | 3              |
  | powierzchnia czytana wlasnym kodem         | 2 (po poprawie)|

* Dwie mutacje NIE upadly za pierwszym razem i wymusily poprawe TESTU:
  - podmiana wartosci pola surface na pusty lancuch przechodzila, bo zaden
    przypadek nie URUCHAMIAL budowy opisu; test buduje teraz opis na atrapie
    produktu i atrapie klasyfikatora,
  - rozluznienie bialej listy powierzchni w regule krokow przechodzilo, bo
    zaden przypadek nie podawal nazwy spoza slownika; doszly dwa: polska nazwa
    i zapis wielkimi literami.

STAN PO WYDANIU
  | Miara                  | 3.30.0 | 3.31.0 |
  |------------------------|--------|--------|
  | zestawow testow        | 37     | 38     |
  | asercji                | 2 288  | 2 347  |
  | relacji spojnosci      | 42     | 43     |
  | autorow opisu produktu | 8      | 1      |
  | bledow lintera PHP     | 0      | 0      |
  | bledow skladni JS      | 0      | 0      |

= 3.30.0 =

ZAPIS ANALIZ ZDJEC: NIE PLACIMY DWA RAZY ZA TE SAMA ODPOWIEDZ
* Zgloszenie: "czy realne jest tworzenie cache z wczesniej wykonanych analiz i
  odczytu powierzchni, zeby przy ponownym uzyciu tego samego zdjecia skrocic
  czas tylko do samego generowania. Oszczedzamy tokeny, czas i konwersje."
* POMIAR PRZED ZMIANA

  | Co zmierzone                              | Stan                    |
  |-------------------------------------------|-------------------------|
  | hashowanie plikow w kodzie                | 0 wystapien             |
  | odczyt usageMetadata z odpowiedzi API     | 0 wystapien             |
  | licznik zuzycia                           | stala 5000 na generacje |
  | reuzycie analizy                          | tylko po numerze zalacznika |
  | pol analizy od modelu                     | 17                      |
  | pol dopisywanych przez zamowienie         | 6                       |

* Reuzycie istnialo (PP_RV_Photo_History), ale kluczem byl numer zalacznika,
  wiec dzialalo wylacznie po kliknieciu miniatury, dla zalogowanych i dla
  trzech ostatnich zdjec. Klient, ktory wgral ten sam plik drugi raz, placil
  za te sama analize jeszcze raz.

CO POWSTALO
* Tabela pp_rv_analiza_cache. Klucz: skrot pliku plus odcisk warunkow.
* PP_RV_Cache_Analizy, czysta domena: co wolno zapisac, jak wyglada klucz, kto
  ma prawo do pliku, jak liczy sie oszczednosc. Bez bazy i bez WordPressa, wiec
  caly rachunek da sie odegrac z wiersza polecen.
* PP_RV_Cache_Store: tabela, liczniki zdarzen, statystyki.
* Bramka stoi w analyze_room, a NIE w obsludze wgrania. analyze_room wolaja trzy
  sciezki: wgranie, ratunek zdjecia i osobny analizator pomieszczenia. Bramka
  przy jednym wolajacym zostawilaby dwie placace za to samo.

TRZY ZASADY, KTORE PILNUJA POPRAWNOSCI
* ZAPISUJEMY WYLACZNIE ODPOWIEDZ O ZDJECIU, 17 pol. Szesc pol nalezacych do
  zamowienia (wall_layout, wall_count, user_selected_color, rejected,
  accepted_by_user oraz braki powierzchni w issues) nie trafia do zapisu nigdy.
  Tak wlasnie psulo sie dotychczasowe reuzycie: zdjecie odrzucone dlatego, ze
  klient zaznaczyl sufit, nie bylo pozniej uzywane nawet dla samej podlogi.
* WERDYKT LICZY SIE NA NOWO przy kazdym odczycie, bo zalezy od progu jakosci z
  ustawien. Zapisany razem z analiza zamrozilby decyzje sprzed zmiany progu.
* ANALIZE WOLNO DZIELIC MIEDZY KONTAMI, PLIKU NIE. Opis wnetrza nie wskazuje na
  osobe, zalacznik owszem. Dwoch gosci ma to samo user_id rowne zero, wiec
  zeru nie oddajemy pliku nigdy.

KLUCZ UNIEWAZNIA SIE SAM
* Odcisk liczony z TRESCI promptu i z identyfikatora modelu. Zmiana instrukcji
  albo modelu uniewaznia zapisy bez pamietania o podbijaniu numeru wersji.

SKROT LICZONY W PRZEGLADARCE, PRZED WYSYLKA
* crypto.subtle liczy sha256 lokalnie, front pyta pp_rv_znam_zdjecie jednym
  krotkim zadaniem. Przy trafieniu plik NIE opuszcza urzadzenia: zdjecie z
  telefonu to 3 do 5 MB, czyli na slabym LTE zwykle dluzej niz sama analiza.
* Cisza zamiast bledu: brak crypto.subtle, blad endpointu albo skasowany
  zalacznik konczy sie zwyklym wgraniem. Rozpoznanie jest przyspieszeniem, a
  nie warunkiem dzialania.
* Endpoint jest TYLKO dla zalogowanych, bo moze oddac zalacznik.

RZECZYWISTY LICZNIK TOKENOW
* PP_Insp_Gemini_Odpowiedz::zuzycie() czyta usageMetadata, razem z
  thoughtsTokenCount modeli myslacych. Rozumowanie jest doliczane, bo sklep za
  nie placi; ponowienie po MAX_TOKENS tez sie DOLICZA, a nie zastepuje.
* increment_usage dostaje liczbe zmierzona. Stala 5000 zostaje wylacznie jako
  wartosc zapasowa, gdy API nie poda zuzycia, i siedzi za filtrem
  pp_rv_szacunek_tokenow. Zero podstawialoby "za darmo", co nie jest prawda.

STATYSTYKI W PANELU
* Nowa sekcja: znane zdjecia, uzycia zapisu, nowe analizy, skutecznosc,
  pominiete wysylki, sredni koszt analizy w tokenach.
* Kwota w zlotowkach pojawia sie dopiero po podaniu ceny tysiaca tokenow.
  Zero znaczy NIE WIEM, nie "za darmo": wymyslona cena wygladalaby w raporcie
  dokladnie tak samo jak zmierzona.
* Sredni koszt liczony WYLACZNIE z wierszy, ktore maja pomiar. Wliczanie zer z
  czasow sprzed licznika zanizaloby wynik i raport chwalilby sie mniejsza
  oszczednoscia niz prawdziwa.

PASEK POSTEPU DLA KROTSZEJ SCIEZKI
* Przy trafieniu odcinek przed generowaniem trwa 1 do 3 sekund zamiast 20 do 40.
* Mediany rozdzielone na dwie sciezki. Wspolna mediana nie opisywalaby zadnej z
  nich, wiec skala bylaby falszywa dla obu grup klientow naraz.
* Zacisk dolny GRANICA_MIN nie obowiazuje ponizej progu 6 sekund. Zmierzone:

  | Odcinek przed | Udzial rzeczywisty | Pasek przed | Pasek po |
  |---------------|--------------------|-------------|----------|
  | 1 s           | 3%                 | 25%         | 3%       |
  | 3 s           | 10%                | 25%         | 10%      |
  | 7 s           | 20%                | 25%         | 25%      |
  | 32 s          | 53%                | 53%         | 53%      |

* Pominiety kafel jest WYGASZONY z podpisem "znane zdjecie, pomijamy", a nie
  ukryty: znikniecie kafla przesuwa pozostale i wyglada jak usterka renderowania.

DWIE WADY ZNALEZIONE PRZY OKAZJI, OBIE ZMIERZONE
* PP_RV_Schema i PP_Room_Visualizer_DB nie byly w mapie aktywatora. Ich tabele
  zakladal wylacznie hak aktywacji albo sciezka bramkowana numerem wersji bazy,
  a hak aktywacji NIE odpala sie przy podmianie wtyczki nowsza paczka. To ta
  sama dziura, ktora zamknal aktywator dla warstwy growth w 3.24.1; galeria i
  zadania po prostu nigdy nie zostaly do niego dopisane. Teraz sa, razem ze
  sprawdzeniem przez SHOW TABLES.
* Wspolny renderer przelacznikow rysowal samo pole wyboru, bez pola ukrytego.
  Przegladarka nie wysyla niezaznaczonego pola, a options.php zapisuje tylko
  to, co przyszlo, wiec zadnego przelacznika wizualizatora nie dalo sie
  WYLACZYC z panelu. Wlaczanie dzialalo i to maskowalo wade. Pulapka jest
  opisana w naszej wlasnej dokumentacji jako numer 12: opis nie zastepuje testu.

TESTY ZMIENIONE, NIE NAPRAWIONE
* aktywacja.test.php: cztery asercje mowily "cztery tabele", bo tyle bylo w
  chwili pisania. Piata zapalila wszystkie naraz przy poprawnym kodzie. Liczba
  czytana jest teraz Z MAPY, a raport porownywany jako zalozone plus istniejace.
* Skan wlascicieli tabel obejmuje wszystkie prefiksy PP_ i WSZYSTKIE tabele
  wymienione w pliku. Branie pierwszej z brzegu kazalo mapie deklarowac cudza
  tabele: PP_RV_Schema wymienia najpierw pp_rv_gallery, ktorej nie zaklada.
* skala-paska.test.js: asercja o medianie poszerzona o argument sciezki.

DOWODY MUTACYJNE
  | Mutacja                                       | Upadlo asercji |
  |-----------------------------------------------|----------------|
  | pole zamowienia wchodzi do zapisu             | 5              |
  | braki powierzchni zostaja w zapisie           | 3              |
  | pusta analiza uznana za warta zapisu          | 3              |
  | odcisk pomija model                           | 3              |
  | plik oddawany kazdemu                         | 6              |
  | goscie utozsamieni ze soba                    | 3              |
  | hash przyjmowany bez sprawdzenia              | 6              |
  | brak ceny udaje cene znana                    | 3              |
  | srednia liczona takze z zer                   | 3              |
  | brak unikalnego klucza w tabeli               | 3              |
  | komentarz wchodzi do CREATE TABLE             | 3              |
  | licznik przyjmuje dowolna nazwe               | 1 (po poprawie)|
  | bramka przenosi sie poza analyze_room         | 4              |
  | do zapisu idzie pelna analiza                 | 3              |
  | werdykt nie liczy sie po odczycie             | 4              |
  | zuzycie znowu jest stala                      | 3              |
  | ponowienie nie dolicza sie                    | 3              |
  | rozumowanie modelu pomijane                   | 3              |
  | zacisk dolny wraca dla krotkiego odcinka      | 4              |
  | endpoint oddaje plik bez sprawdzenia          | 3              |
  | endpoint nie sprawdza pliku                   | 3              |
  | rozpoznanie otwarte dla gosci                 | 1              |
  | skala nie rozroznia sciezek                   | 3              |
  | front nie pyta przed wysylka                  | 1              |
  | brak pola ukrytego przy przelaczniku          | 2              |

* Jedna mutacja NIE upadla za pierwszym razem: wylaczenie warunku odrzucajacego
  nazwe licznika spoza listy. Asercja pytala o obecnosc tablicy dozwolonych
  nazw, a nie o to, czy cokolwiek strzeze. Test poprawiony, potem mutacja
  upadla.
* Pierwszy przebieg mutacyjny przerwal sie na limicie czasu i zostawil jeden
  plik w stanie zmutowanym. Nastepny przebieg policzyl wtedy baze jako dwa
  bledy i trzy mutacje wygladaly na niewykryte. Skrypt mutacyjny musi
  sprawdzac czystosc drzewa PRZED startem.

STAN PO WYDANIU
  | Miara                  | 3.29.0 | 3.30.0 |
  |------------------------|--------|--------|
  | zestawow testow        | 36     | 37     |
  | asercji                | 2 165  | 2 288  |
  | relacji spojnosci      | 42     | 42     |
  | tabel wlasnych         | 8      | 9      |
  | bledow lintera PHP     | 0      | 0      |
  | bledow skladni JS      | 0      | 0      |

= 3.29.0 =

PASEK POSTEPU ZATRZYMYWAL SIE, BO ODPYTYWANIE SAMO GENEROWALO
* Zgloszenie: "pasek postepu zatrzymuje sie, a potem od razu pokazuje sie
  wizualizacja, przez to nie widac, czy cos sie dzieje. Czas tez nie jest
  liczony, pasek nie jest adekwatny do biezacego stanu realizacji."
* ZMIERZONA PRZYCZYNA. handle_status, czyli endpoint pytany co 3 sekundy o
  postep, wolal pipeline->cron_process_job(), czyli PELNE generowanie.
  Przegladarka ma straznik _isPolling: dopoki poprzednie odpytanie nie
  wrocilo, nastepne sie nie wysyla. Odpytanie, ktore samo generowalo, nie
  wracalo przez cala generacje.
* Przebieg 21 s kolejki i 44 s generowania, odtworzony na PRAWDZIWYM kodzie
  (PP_RV_Postep z PHP przez wiersz polecen plus opisCzekania z pliku JS):

  | Miara                                   | Przed | Po  |
  |-----------------------------------------|-------|-----|
  | odpytan wstrzymanych straznikiem        |    14 |   0 |
  | sekund bez ruchu paska (z 65)           |    58 |   9 |
  | najdluzszy zastoj na jednej wartosci    |  46 s | <3s |
  | sekund z widocznym etapem generowania   |     0 |  45 |
  | skok koncowy                            |28>100 |94>100|

* Etap "Generowanie" nie pokazywal sie ANI RAZU. Klient widzial kolejke,
  potem nic przez czterdziesci kilka sekund, potem gotowa wizualizacje.

PRACA PRZENIESIONA DO OSOBNEGO ZADANIA HTTP
* Nowa akcja pp_rv_praca, TYLKO dla zalogowanych (generowanie jest platne, a
  endpoint odpytywania jest otwarty takze dla gosci).
* handle_status jest teraz wylacznie odczytem i wraca natychmiast. Dzieki temu
  status 'generating', zapisywany przez potok PRZED wywolaniem modelu, jest
  widoczny przez caly czas jego trwania.
* Odpowiedz statusu niesie pole wymaga_pracy. Front wysyla prace RAZ i NIE
  czeka na nia: odpytywanie biegnie dalej co trzy sekundy.
* Blokada zostaje bez zmian, bo dwa rownolegle wywolania uruchomilyby dwa
  platne generowania tego samego zadania.
* Regula "kiedy ruszyc zadanie" jest CZYSTA i sprawdzalna z wiersza polecen:
  PP_Room_Visualizer_Ajax::czy_wymaga_pracy( status, wiek, kolejka, karencja ).

  | status     | wiek | kolejka | wynik                        |
  |------------|------|---------|------------------------------|
  | queued     |  5 s | dziala  | czekamy, kolejka to zrobi    |
  | queued     | 20 s | dziala  | PRACA, karencja minela       |
  | queued     |  0 s | brak    | PRACA, nie ma na co czekac   |
  | analyzing  |300 s | brak    | nie ta sciezka, osobny prog  |
  | generating |  5 s | dziala  | juz sie dzieje               |

* Przy braku Action Schedulera karencja 20 s zostala zniesiona. Czekanie
  dwudziestu sekund na cos, czego nie ma, to bylo czyste opoznienie doliczone
  do kazdej wizualizacji.

ZEGAR LOKALNY: EKRAN ZYJE MIEDZY ODPOWIEDZIAMI
* Przeniesienie pracy usuwa JEDNO zrodlo ciszy. Zegar usuwa SKUTEK ciszy w
  ogole: wolnej sieci, uspionej karty, zgubionej odpowiedzi.
* PPRoomViz.tykZegara, funkcja czysta, uruchamiana co sekunde:
  - uplyw jest FAKTEM i leci dalej zawsze,
  - szerokosc jest PRZEDLUZENIEM tempa zmierzonego miedzy dwiema ostatnimi
    odpowiedziami serwera, najwyzej przez 15 sekund ciszy, potem pasek STOI,
  - po 12 sekundach ciszy ekran MOWI o niej wprost.
* Pasek pelznacy w nieskonczonosc przy martwym serwerze to ta sama wada,
  ktora naprawiamy, tylko odwrocona. Stad twarde granice zamiast animacji.
* Zegar zatrzymywany jest w JEDNYM miejscu, razem z odpytywaniem. Osobne
  zatrzymania przy done, failed i przekroczeniu czasu znaczylyby, ze pierwsza
  nowa sciezka wyjscia zostawi tykajacy interwal piszacy po martwym ekranie.

NOWA RELACJA SPOJNOSCI (42)
* "Endpoint odpytywany w petli i endpoint dla gosci nie uruchamiaja potoku".
  Relacja pyta o KLASE wady, nie o jedno miejsce: sprawdza kazdy handler akcji
  konczacej sie na _status oraz kazdy handler zarejestrowany dla gosci.
* Pierwsza redakcja relacji dala FALSZYWY ALARM: brala okno 9000 znakow od
  poczatku metody, a handle_generate ma 232 linie, wiec okno wchodzilo w
  nastepna metode i przypisywalo jej cudze wywolanie. Cialo metody konczy sie
  teraz na nastepnej metodzie. Falszywy alarm w relacji jest gorszy niz jej
  brak, bo uczy ignorowania czerwonego paska w panelu.

TEST ZMIENIONY, NIE NAPRAWIONY
* Relacja "Kazdy nieterminalny status zadania ma droge wyjscia" wymagala, zeby
  wyjscie bylo w handle_status. Po przeniesieniu pracy status 'analyzed' nie
  wystepowal tam juz i relacja sie zapalila. Wymog zostaje ten sam, ale
  szukamy w OBU sciezkach ratunkowych. Pomiar uzasadniajacy zmiane: tabela
  wyzej, 46 sekund zastoju wynikalo wlasnie z tego, ze praca byla w
  handle_status.
* Asercja o wersji w dokumentacji zaostrzona. Poprzednia szukala numeru
  GDZIEKOLWIEK w pliku i przechodzila przez przypadek: zdanie "do 3.28.2
  robilo to jedno zadanie" w rozdziale o pulapkach wygladalo dla niej jak
  deklaracja wersji biezacej. Teraz czyta wylacznie naglowek.

DOWODY MUTACYJNE
  | Mutacja                                  | Upadlo asercji |
  |------------------------------------------|----------------|
  | praca wraca do odpytania                 | 1              |
  | serwer przestaje wysylac wymaga_pracy    | 1              |
  | brak kolejki nie znosi karencji          | 3              |
  | praca bez blokady                        | 1              |
  | praca otwarta dla gosci                  | 1              |
  | brak strazy przed wielokrotna wysylka    | 1              |
  | wysylka pracy wstrzymuje odpytywanie     | 1              |
  | zegar nie jest zatrzymywany              | 2              |
  | przedluzenie bez granicy                 | 1              |
  | cisza nienazwana                         | 2              |
  | sufit lokalny zniesiony                  | 2              |
  | zegar nie startuje                       | 1              |
  | potok wraca do handle_status (relacja)   | 1              |
  | praca otwarta dla gosci (relacja)        | 1              |
  | znika droga wyjscia dla analyzed         | 1              |

* Cztery mutacje NIE upadly za pierwszym razem i wymusily poprawe TESTU:
  obecnosc blokady zamiast jej kolejnosci, nazwa _pracaWyslana zamiast
  warunku, sufit nigdy nietkniety przez zaden przypadek, oraz mutant
  rownowazny (zdjecie warunku tempo > 0 nic nie zmienia, bo zacisk i tak
  podnosi wartosc do wartosci serwera) — ten zostaje opisany w tescie jako
  rownowazny, a nie zamalowany dodatkowa asercja.

STAN PO WYDANIU
  | Miara                  | 3.28.2 | 3.29.0 |
  |------------------------|--------|--------|
  | zestawow testow        | 35     | 36     |
  | asercji                | 2 107  | 2 165  |
  | relacji spojnosci      | 41     | 42     |
  | bledow lintera PHP     | 0      | 0      |
  | bledow skladni JS      | 0      | 0      |

= 3.28.2 =

JEDEN PLIK DOKUMENTACJI, RAZEM Z WTYCZKA
* Zgloszenie: "rozpisz wszystko, wystaw jako jeden plik razem z wtyczka".
* DOKUMENTACJA.md w katalogu glownym wtyczki, 11 rozdzialow, 81 KB.
  Osiem plikow z 3.28.1 zostalo scalonych i uzupelnionych o trzy rozdzialy,
  ktorych wczesniej nie bylo.

  | Rozdzial                          | Stan       |
  |-----------------------------------|------------|
  | 1. Architektura                   | z 3.28.1   |
  | 2. Wizualizator wnetrz            | z 3.28.1   |
  | 3. Zestawy, przelicznik i koszyk  | z 3.28.1   |
  | 4. Panel i ustawienia             | z 3.28.1   |
  | 5. Baza danych, AJAX, shortcode'y | z 3.28.1   |
  | 6. Growth                         | NOWY       |
  | 7. SEO i feed Google Merchant     | NOWY       |
  | 8. Mapa pozostalych modulow       | NOWY       |
  | 9. Testy                          | z 3.28.1   |
  | 10. Pulapki                       | z 3.28.1   |
  | 11. Czego wtyczka NIE robi        | NOWY       |

* Rozdzial 6 opisuje warstwe, ktora w 3.28.1 byla pokazana wylacznie z lotu
  ptaka: 28 klas, okolo 16 000 linii, przeplyw tozsamosc > zdarzenie > fakt >
  decyzja > dzialanie, katalog 32 zdarzen, 5 warunkow, 7 akcji, dwie ROZNE
  bramki przed wysylka (zgoda i polityka kontaktu).
* Rozdzial 8 wymienia KAZDA pozostala klase z jednym zdaniem i liczba linii,
  zeby dalo sie znalezc wlasciwy plik bez czytania stu dwudziestu.
* Rozdzial 11 spisuje ograniczenia i wyjscia awaryjne. Ograniczenie
  nieopisane wyglada jak usterka i ktos zaczyna je naprawiac od nowa.

DLACZEGO JEDEN PLIK, A NIE OSIEM
* W 3.28.1 ta sama liczba (liczba zestawow testow) stala w dwoch dokumentach.
  Test musial pilnowac zgodnosci MIEDZY nimi, bo dwie rozne liczby w
  dokumentacji sa gorsze niz jedna nieaktualna: czytelnik nie wie, ktorej ufac.
  Scalenie usunelo caly ten rodzaj rozjazdu.
* Rozbita seria zostala usunieta z docs. Zostawienie jej obok scalonego pliku
  dalo by dwa zrodla prawdy, czyli dokladnie wade opisana w rozdziale 10.
* Piec starszych dokumentow (RAPORT_AUDYTU, ZMIANY-3.7.0, INWENTARZ_OPCJI,
  INSTALACJA, INTEGRACJA-GEN) zostaje jako materialy historyczne, wprost
  oznaczone w rozdziale wstepnym.

TEST DOKUMENTACJI ROZSZERZONY
* tests/dokumentacja.test.js: 92 asercje > 222 asercje.
* Nowe grupy sprawdzen:
  - 32 nazwy zdarzen growth wystepuja w katalogu PP_Insp_Event_Ledger,
  - 5 warunkow i 7 akcji wyzwalaczy istnieje w klasach, ktore je rejestruja,
  - 16 stalych i opcji growth (crony, ciasteczka, parametry linkow),
  - 8 metod PP_RV_Feed_Product i 6 metod PP_Insp_Gmc_Optimizer,
  - 28 plikow wymienionych w mapie modulow istnieje.
* Nowe sprawdzenie kotwic: kazdy odnosnik wewnetrzny ]( #... ) musi trafiac w
  istniejacy naglowek. W jednym pliku odnosnik nie moze juz wskazac
  nieistniejacego pliku, ale MOZE wskazac nieistniejaca kotwice, a klikniecie
  nie robi wtedy nic.
* Bloki kodu sa wycinane PRZED zbieraniem naglowkow. Komentarz powloki
  "# Wszystko naraz" wyglada w markdownie dokladnie jak naglowek, wiec bez tego
  kotwica #wszystko-naraz bylaby uznana za istniejaca.

CO TEST ZLAPAL OD RAZU
  | Twierdzenie                       | Stan po scaleniu | Poprawka        |
  |-----------------------------------|------------------|-----------------|
  | wersja w dokumentacji             | 3.28.1           | 3.28.2          |
  | liczba zestawow testow            | 36               | 35 (zmierzone)  |
  | kotwice #1-architektura i inne    | 4 martwe         | filtr naglowkow |
  | rozbita seria docs                | nadal obecna     | usunieta        |

* Wszystkie cztery bledy byly w tekscie, ktory wygladal poprawnie. Zadnego z
  nich nie dalo sie zauwazyc czytaniem.

STAN PO WYDANIU
  | Miara                  | 3.28.1 | 3.28.2 |
  |------------------------|--------|--------|
  | zestawow testow        | 35     | 35     |
  | asercji                | 1 977  | 2 107  |
  | relacji spojnosci      | 41     | 41     |
  | bledow lintera PHP     | 0      | 0      |
  | bledow skladni JS      | 0      | 0      |
  | plikow dokumentacji    | 8      | 1      |

* ZERO zmian w kodzie produkcyjnym. Dokumentacja, test dokumentacji i numer
  wersji. Ograniczenia z 3.28.0 pozostaja w mocy i sa opisane w rozdziale 11:
  rabaty nie trafiaja do koszyka WooCommerce (nie wlaczaj ich), okno ilosci w
  wizualizatorze nie pobiera pozycji montazowych.

= 3.28.1 =

DOKUMENTACJA DLA PROGRAMISTY
* Zgloszenie: "stworz w markdown dokumentacje, co za co odpowiada, ktora
  logika i co, rozpisz dokumentacje dla programisty, ktory nie zna w ogole
  kodu, opisz krotko i zrozumiale logike funkcji".
* Osiem dokumentow w katalogu docs, pisanych dla kogos, kto widzi ten kod
  pierwszy raz.

  | Plik                     | O czym                                       |
  |--------------------------|----------------------------------------------|
  | README.md                | Od czego zaczac, mapa, konwencje             |
  | 01-architektura.md       | Ladowanie, warstwy, katalogi, czysta domena  |
  | 02-wizualizator.md       | Potok generowania krok po kroku              |
  | 03-zestawy-i-koszyk.md   | Okno zakupu, przelicznik, rabaty             |
  | 04-ustawienia.md         | Strony w panelu, opcje, pulapki formularzy   |
  | 05-baza-i-api.md         | Tabele, 88 akcji AJAX, shortcode'y, filtry   |
  | 06-testy.md              | Uruchamianie, konwencje, dowody mutacyjne    |
  | 07-pulapki.md            | 14 wad, ktore juz wystapily, i jak ich unikac|

* Dokumentacja NIE jest opisem zamierzen. Kazda tabela zawiera liczby
  zmierzone w repozytorium: 122 pliki PHP, 38 JS, 26 arkuszy CSS, 88 akcji
  AJAX, 8 tabel, 10 shortcode'ow, 11 stron w panelu.
* Kazda wada opisana w 07-pulapki.md faktycznie wystapila i kosztowala wydanie
  albo kilka. Sa tam liczby, nie ostrzezenia ogolne: ile bednych zapytan
  wysylal dbDelta, ile odczytow paska stalo w miejscu, od ktorego wydania
  arkusze sie nie ladowaly.

DOKUMENTACJA JEST SPRAWDZANA TESTEM
* Nowy zestaw tests/dokumentacja.test.js, 92 asercje.
* Dokumentacja z bledem jest GORSZA niz jej brak: programista uwierzy i pomyli
  sie w miejscu, ktorego by nie ruszyl, gdyby nic nie wiedzial. Dokumentacja
  starzeje sie cicho, bo nikt jej nie uruchamia.
* Test sprawdza KAZDE twierdzenie faktograficzne:
  - czy wymieniony plik istnieje (18 plikow),
  - czy metoda ma taka nazwe (27 metod w piatce klas),
  - czy akcja AJAX jest zarejestrowana (12 akcji),
  - czy shortcode istnieje (10),
  - czy filtr istnieje (6),
  - czy tabela istnieje (6),
  - czy odnosniki miedzy dokumentami prowadza donikad,
  - czy liczby w dokumentacji zgadzaja sie ze stanem repozytorium.

* Test zadzialal od razu po dopisaniu: dodanie go samego zmienilo liczbe
  zestawow z 34 na 35, a dokumentacja nadal mowila 34. Dokladnie po to
  istnieje.

DWIE POPRAWKI SAMEGO TESTU, ZANIM WSZEDL
* Liczba zestawow wystepuje w DWOCH dokumentach. Sprawdzenie tylko jednego
  przepuscilo by rozjazd, a dwie rozne liczby w dokumentacji sa gorsze niz
  jedna nieaktualna: czytelnik nie wie, ktorej ufac. Test sprawdza oba.
* Liczba asercji zapisana jest z odstepem tysiecy, wiec porownanie znak po
  znaku dawaloby falszywy alarm przy kazdym wydaniu. Test wyciaga cyfry i
  porownuje liczby.

CO Z ISTNIEJACYMI DOKUMENTAMI
* W katalogu docs lezaly juz cztery starsze pliki, w tym RAPORT_AUDYTU.md na
  105 KB i ZMIANY-3.7.0.md na 164 KB. NIE usunalem ich, bo moga zawierac
  ustalenia, ktorych nie znam. README wprost mowi, ktore sa aktualne, a ktore
  historyczne, zeby nikt nie czytal opisu sprzed dwudziestu wydan jako prawdy.

CZEGO TA WERSJA NIE ZMIENIA
* Ani jednej linii kodu produkcyjnego. Wylacznie dokumentacja, jeden nowy
  zestaw testow i podbicie wersji.
* Ograniczenia opisane w 3.28.0 zostaja: rabaty nie trafiaja do koszyka
  WooCommerce, a okno ilosci w wizualizatorze nie dostaje pozycji montazowych.
  Oba sa wymienione w dokumentacji jako znane ograniczenia, a nie przemilczane.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.28.0 | 3.28.1 |
  |------------------------|--------|--------|
  | zestawow               |     34 |     35 |
  | asercji zaliczonych    |  1 885 |  1 977 |
  | relacji spojnosci      |     41 |     41 |
  | plikow PHP po lincie   |    157 |    157 |
  | plikow JS po lincie    |     52 |     52 |
  | dokumentow w docs      |      5 |     13 |


= 3.28.0 =

JEDEN MODUL ZAKUPU ZAMIAST DWOCH
* Zgloszenie: "wykorzystaj gotowy modul juz z quick view inspiracji, ujednolic
  i oczysc kod ze starej logiki dodawania i edycji produktow, zastosuj go na
  pojedynczej karcie inspiracji oraz osadz w wizualizatorze. Ta logika jest
  najlepsza".

  Pomiar przed zmiana: DWA handlery na ten sam przycisk
  | Plik                    | Co robil                                    |
  |-------------------------|---------------------------------------------|
  | pp-inspirations-buy.js  | otwiera okno konfiguracji zestawu            |
  | public.js               | dodaje WSZYSTKO od razu, bez okna, z alert() |

* Modul zakupu robil na starcie $(document).off() na przycisku, czyli kasowal
  cudzy nasluch w czasie dzialania. To maskowalo, ze sa dwa, i dzialalo
  WYLACZNIE wtedy, gdy modul sie zaladowal. Przy wylaczonej opcji
  pp_insp_buy_enable albo na stronie spoza jego warunku zostawal handler stary
  i klient dostawal alert zamiast okna.
* Stary handler USUNIETY U ZRODLA, 45 linii. Zdejmowanie cudzego nasluchu tez
  usuniete, bo nie ma juz czego zdejmowac, a gdyby wrocilo, awaria bedzie
  widoczna od razu.
* Po zmianie: jeden handler w calym katalogu assets.

TEN SAM PRZYCISK NA TRZECH EKRANACH
  | Ekran                          | Ma wspolny przycisk |
  |--------------------------------|---------------------|
  | karta pojedynczej inspiracji   | tak                 |
  | wizualizator                   | tak                 |
  | okno zestawu                   | tak                 |

* Karta pojedynczej inspiracji NIE dostala ani jednej nowej linii kodu.
  Dziala, bo przestal ja przechwytywac stary handler. To jest cala roznica
  miedzy ujednoliceniem a dopisaniem czwartej implementacji.

SEKCJA MONTAZOWA W OKNIE ZESTAWU
* Pod produktami glownymi doszla sekcja z akcesoriami, ktorych ilosci licza sie
  z zapotrzebowania calego zestawu.
* Zapotrzebowanie liczone z WYBRANYCH pozycji: powierzchnia z produktow
  rozliczanych na m2, obwod z rozliczanych na mb, paczki jako suma sztuk.
  Kazda podstawa z wlasnego zrodla, bo mnozenie metrow biezacych przez metry
  kwadratowe dawaloby liczbe bez znaczenia.
* DOMYSLNIE NIEZAZNACZONE. To nie jest ostroznosc, tylko wymog prawa: art. 10
  ustawy o prawach konsumenta wymaga WYRAZNEJ zgody na kazda doplate. Zgoda
  domniemana z zaznaczonego pola jest nieskuteczna i uprawnia klienta do
  zwrotu doplaty.
* POLICZONE mimo niezaznaczenia, bo klient ma widziec, ILE by tego bylo, zanim
  zdecyduje. Pole z pusta iloscia kaze mu liczyc samemu.
* Kazda pozycja mowi, SKAD wziela sie ilosc, na przyklad "z obwodu 18,9 mb,
  1 szt. na 8 mb".
* Reczna zmiana ilosci ZAZNACZA pozycje. Klient, ktory wpisuje liczbe, chce tej
  pozycji, a osobne klikniecie byloby druga czynnoscia dla tej samej decyzji.
* Niezaznaczona pozycja jest WYGASZONA, nie ukryta.

RABAT ZA KOMPLETNY ZESTAW
* Widoczny ZANIM klient cokolwiek zaznaczy. To jest powod, dla ktorego mialby
  zaznaczyc, wiec pokazany po fakcie nie ma juz wplywu.
* Nalicza sie WYLACZNIE wtedy, gdy klient wzial takze montaz. Obietnica brzmi
  "rabat przy zakupie kompletnego zestawu", wiec naliczanie go za sam zestaw
  glowny byloby jej zlamaniem.
* Rabat nie przekracza ceny, a procentowy jest zaciskany do stu. Ujemna
  pozycja w koszyku to nie promocja, tylko blad ksiegowy, ktory WooCommerce
  przyjmie bez slowa.
* Kwota w stopce obejmuje montaz i rabat. Musi byc ta sama, ktora klient
  zaplaci, inaczej okno klamie w jedynym miejscu, gdzie klamac nie wolno.

JEDNO ZADANIE DO KOSZYKA
* Montaz idzie TYM SAMYM zadaniem, co zestaw glowny. Osobne wywolanie dawaloby
  dwa koszyki przy pierwszej awarii sieci: produkty w srodku, akcesoria nie, i
  klient nie wie, co ma.
* Brak odpowiedzi z endpointu NIE blokuje zakupu. Sekcja zostaje ukryta, a
  zestaw glowny da sie kupic jak wczesniej: awaria dodatku nie moze zabrac
  mozliwosci zlozenia zamowienia.

ENDPOINT AKCESORIOW
* Dostepny takze dla niezalogowanych, bo zestaw kompletuje sie przed
  zalozeniem konta.
* Pomija pozycje z zerowa iloscia, nieaktywne reguly i produkty spoza sklepu.
  Pozycja z iloscia zero to pozycja widmo: zajmuje miejsce, niczego nie wnosi i
  kaze klientowi zgadywac, czemu tam jest.

OBA OKNA MOWIA TYM SAMYM JEZYKIEM
  | Element    | Pozycja glowna | Pozycja montazowa |
  |------------|----------------|-------------------|
  | nazwa      |          15 px |             13 px |
  | miniatura  |          44 px |             34 px |
  | pole ilosci|              — |    44 px, cel dotyku |
  Te same miary w oknie zestawu i w oknie ilosci wizualizatora. Rozjazd
  znaczylby, ze klient widzi dwa rozne jezyki dla tej samej rzeczy, zaleznie
  od tego, skad wszedl. Test porownuje oba arkusze ze soba.

DOWODY MUTACYJNE: 14 mutacji, 14 wykrytych
  | Mutacja                                        | Asercji upadlo |
  |------------------------------------------------|----------------|
  | wraca stary handler w public.js                |              1 |
  | montaz domyslnie ZAZNACZONY                    |              1 |
  | ilosc pusta zamiast policzonej                 |              1 |
  | rabat za komplet takze bez montazu             |              1 |
  | montaz nie wchodzi do sumy                     |              1 |
  | montaz idzie osobnym zadaniem                  |              2 |
  | rabat moze przekroczyc cene                    |              1 |
  | metry biezace mieszaja sie z kwadratowymi      |              1 |
  | zdarzenia wiazane do wezla, nie delegowane     |              1 |
  | endpoint wpuszcza pozycje zerowe               |              1 |
  | endpoint niedostepny dla niezalogowanych       |              1 |
  | pozycja montazowa tej samej wielkosci co glowna|              2 |
  | niezaznaczona pozycja ukryta zamiast wygaszonej|              1 |
  | pole ilosci ponizej progu dotyku               |              1 |

ASERCJA POPRAWIONA, BO MIERZYLA NIE TO, CO TRZEBA
* Sprawdzenie "koszyk dostaje jedno zadanie" liczylo WSZYSTKIE wysylki w pliku
  i naliczylo trzy, choc chodzilo o to, zeby okno zestawu wysylalo jedna.
  Przy okazji pokazalo, ze sciezek wysylki sa trzy: refresh dla pojedynczego
  produktu, submitSet dla okna zestawu i submitIntegratedSet dla paska w
  modalu galerii. To nie sa duplikaty do scalenia w tym wydaniu, bo kazda
  obsluguje inny ekran i inny zestaw pozycji. Zapisane jako pomiar, zeby przy
  nastepnym ujednoliceniu bylo od czego zaczac.

CZEGO TA WERSJA NIE ROBI
* Rabaty nadal nie trafiaja do koszyka WooCommerce. Okno pokazuje kwote po
  rabacie, a koszyk policzy cene katalogowa. To osobna, wrazliwa robota na
  cenach i idzie nastepnym wydaniem.
* Okno ilosci w wizualizatorze, podpiete pod "Zapisz ilosci", nadal nie
  dostaje pozycji montazowych z serwera. Ma je teraz skad wziac, bo endpoint
  istnieje, ale podpiecie idzie razem z rabatami w koszyku.
* Trzy sciezki wysylki zestawu zostaja. Scalanie ich to osobna zmiana.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.27.1 | 3.28.0 |
  |------------------------|--------|--------|
  | zestawow               |     33 |     34 |
  | asercji zaliczonych    |  1 831 |  1 885 |
  | relacji spojnosci      |     41 |     41 |
  | plikow PHP po lincie   |    157 |    157 |
  | plikow JS po lincie    |     52 |     52 |
  | arkuszy CSS            |     26 |     26 |


= 3.27.1 =

ZASOBY STRONY USTAWIEN NIE LADOWALY SIE ANI RAZU
* Zgloszenie: "nie dziala wyszukiwarka, nie podpowiada zadnych, brakuje styli,
  jakby cos nie dzialalo w ogole, nie ma logiki dzialania".
* Wszystkie trzy objawy mialy JEDNA przyczyne.

  Dwa straznice w jednym pliku, dwa rozne wzorce
  | Funkcja                      | Wzorzec              | Skutek     |
  |------------------------------|----------------------|------------|
  | enqueue_admin_assets         | 'pp-room-visualizer' | dziala     |
  | enqueue_catalog_rules_assets | 'pp-rv'              | NIE dziala |

  Zmierzone wykonaniem
  | Co sprawdzane                          | Wynik |
  |----------------------------------------|-------|
  | strpos('pp-inspirations_page_pp-room-visualizer', 'pp-rv') | false |
  | strpos('pp-room-visualizer', 'pp-rv')  | false |
  | warunek przepuszcza                    | NIE   |

* Napis 'pp-rv' nie wystepuje ani w slugu strony, ani w jej uchwycie. Warunek
  odrzucal KAZDE wejscie, wiec arkusze i skrypty wybierakow, akcesoriow i menu
  plaszczyzn nie trafily na strone ANI RAZU. Pliki istnialy, sciezki byly
  poprawne, wszystkie dotychczasowe relacje przechodzily.
* Wzorzec 'pp-rv' pasowal wylacznie do strony moderacji 'pp-rv-moderation',
  czyli do jedynej strony, ktora tych zasobow nie potrzebuje.
* Wada siedziala tam od wydania 3.21.0, czyli od chwili, gdy powstal panel
  regul katalogu. Wszystko, co od tamtej pory dopisywalem do tej strony,
  ladowalo sie w prozne.

JEDEN STRAZNIK ZAMIAST DWOCH
* Slug jest teraz stala klasy, a oba kolejkowania ida przez jedna metode
  czy_nasza_strona(). Straznik sprawdza uchwyt ORAZ parametr strony w adresie,
  bo uchwyt zalezy od pozycji w menu i zmienia sie przy jej przestawieniu.

  Zmierzone po naprawie
  | Uchwyt                                  | Wynik       |
  |-----------------------------------------|-------------|
  | pp-inspirations_page_pp-room-visualizer | przepuszcza |
  | toplevel_page_pp-room-visualizer        | przepuszcza |
  | pp-inspirations_page_pp-rv-moderation   | odrzuca     |
  | edit.php                                | odrzuca     |
  | dowolny uchwyt, page w adresie          | przepuszcza |

NOWA RELACJA SPOJNOSCI (40 -> 41): STRAZNIK, KTORY NIGDY NIE PRZEPUSZCZA
* Zadna z czterdziestu poprzednich relacji tego nie widziala, bo kazda
  sprawdzala ISTNIENIE zasobu, a nie to, czy warunek jego zaladowania
  kiedykolwiek jest prawdziwy.
* Relacja wyciaga wzorce z warunkow na uchwycie, wyciaga slugi z rejestracji
  stron i sprawdza, czy wzorzec pasuje do slugu strony rejestrowanej PRZEZ TEN
  SAM PLIK. Ma licznik niewaznosci: zero zbadanych straznikow to nie zaliczenie.
* Wzorzec i slug bywaja literalem ALBO stala klasy. Relacja rozwiazuje oba
  zapisy, bo wersja czytajaca tylko literaly zglosila TRZY poprawne straznice
  jako martwe. Relacja z falszywymi alarmami zostaje wylaczona przy pierwszym
  kontakcie.
* ZAOSTRZENIE DO WLASNEJ STRONY jest tym, co lapie te konkretna wade. Dowod
  parowy: przy slugu przestawionym z powrotem na 'pp-rv' relacja z dopasowaniem
  do wlasnej strony ZGLASZA, a relacja z dopasowaniem do dowolnego slugu wtyczki
  NIE ZGLASZA, bo istnieje 'pp-rv-moderation'.

ASERCJA, KTORA PILNOWALA WADY
* Test panelu regul z 3.21.0 mial asercje "kolejkowane tylko na stronie
  wizualizatora", ktora wymagala DOKLADNIE napisu strpos( $page, 'pp-rv' ).
  Czyli wymagala tego samego napisu, ktory powodowal awarie, i przez szesc
  wydan pilnowala, zeby nie zostal poprawiony. To najgorsza mozliwa forma
  testu: nie tylko nie wykrywa wady, ale ja utrwala.
* Asercja zastapiona wymogiem przejscia przez wspolny straznik znajacy
  prawdziwy slug, plus zakazem powrotu starego wzorca.

DOWODY MUTACYJNE: 7 mutacji
  | Mutacja                                     | Wykryta |
  |---------------------------------------------|---------|
  | slug wraca do martwego wzorca               | tak, 3  |
  | straznik nie czyta parametru adresu         | tak, 2  |
  | znika kolejkowanie skryptu menu             | tak, 1  |
  | relacja nie rozwiazuje stalych slugow       | tak, 1  |
  | relacja wraca do luznego dopasowania        | tak, w parze |
  | relacja bez licznika niewaznosci            | tak, w parze |
  | jedno kolejkowanie omija straznika          | pominieta, wzorzec nie pasowal |

* Dwie mutacje wymagaly PARY, i to jest poprawne: licznik niewaznosci broni
  przed przyszlym zerem, a nie przed obecnym stanem, wiec sam w sobie jest
  niewykrywalny, dopoki straznikow jest wiecej niz zero. Pokazane osobnymi
  przebiegami: po wylaczeniu zbierania straznikow relacja melduje "Zbadano 0
  straznikow, zero zbadanych to nie zaliczenie".

LUKA W TESCIE ZNALEZIONA WLASNYM DOWODEM MUTACYJNYM
* Sprawdzenie kolejkowania szukalo samego uchwytu zasobu i przechodzilo, gdy
  zniknal SKRYPT, bo arkusz o tym samym uchwycie zostawal. Strona ze stylem bez
  zachowania wyglada na dzialajaca, dopoki sie w nia nie kliknie. Sprawdzane sa
  teraz arkusz i skrypt OSOBNO, razem z istnieniem obu plikow.

CZEGO TA WERSJA NIE ROBI
* Nie zmienia niczego w wygladzie ani w logice menu, akcesoriow i okna ilosci.
  Caly kod z 3.26.0 i 3.27.0 byl poprawny, tylko nigdy nie docieral do
  przegladarki.
* Nadal nie ma polaczenia przelicznika akcesoriow z lista produktow na ekranie
  wyniku, wiec sekcja montazowa w oknie ilosci bedzie pusta.
* Rabaty nadal nie trafiaja do koszyka WooCommerce.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.27.0 | 3.27.1 |
  |------------------------|--------|--------|
  | zestawow               |     33 |     33 |
  | asercji zaliczonych    |  1 809 |  1 831 |
  | relacji spojnosci      |     40 |     41 |
  | plikow PHP po lincie   |    157 |    157 |
  | plikow JS po lincie    |     52 |     52 |
  | arkuszy CSS            |     26 |     26 |


= 3.27.0 =

MENU WYKLUCZANIA, DODAWANIA I USTAWIEN
* Zgloszenie: "stworz uzyteczne, przejrzyste menu wykluczania, dodawania i
  ustawien, dopracuj to". Poprzednia forma dostala ocene "najgorsza".

  Pomiar poprzedniej sekcji
  | Co liczone                            | Przed |
  |---------------------------------------|-------|
  | linii renderu                         |   188 |
  | naglowkow "Wykluczone kategorie"      |     2 |
  | naglowkow "Wykluczone produkty"       |     2 |
  | roznic wizualnych miedzy nimi         |     0 |
  | podgladu skutku ustawienia            |     0 |

  Ta sama nazwa dwa razy na jednym ekranie, raz w bloku plaszczyzny i raz w
  bloku wspolnym, bez zadnej roznicy wizualnej. Nie dalo sie odroznic, ktore
  wykluczenie dotyczy czego.

* PLASZCZYZNY JAKO ZAKLADKI. Trzy komplety ustawien plus wspolny staly dotad
  jeden pod drugim. Zakladka pokazuje JEDEN komplet naraz.
* LICZNIK PRZY KAZDEJ ZAKLADCE mowi, ile regul ma dana plaszczyzna, wiec nie
  trzeba wchodzic w kazda, zeby sprawdzic, czy cokolwiek jest ustawione. Zero
  regul ma osobne, wyszarzone oznaczenie.
* BLOK WSPOLNY ODDZIELONY WIZUALNIE, nie tylko naglowkiem: inne tlo, przerywana
  ramka, wlasny znak i zdanie o tym, ze nadpisuje ustawienia plaszczyzn.
* ZNAK PRZY NAGLOWKU: plus dla dodawania, minus dla odbierania. Kolor nie jest
  jedynym nosnikiem znaczenia, bo osoba nierozrozniajaca barw widzialaby wtedy
  dwa identyczne bloki. Symbole ukryte przed czytnikiem ekranu, bo tekst obok
  mowi to samo.
* Zakladki obsluguja klawiature strzalkami, maja cel dotykowy 44 px i
  oznaczenie aria-selected.
* WYBRANA ZAKLADKA PRZEZYWA ZAPIS. Zapis ustawien przeladowuje strone, a powrot
  na pierwsza zakladke po kazdym zapisie kaze szukac od nowa tego, co sie
  wlasnie zmienialo.

* KARTA pp-reg-karta ZOSTAJE. Panel ja wylacznie opakowuje, wiec zaden
  istniejacy styl ani zadne pole nie zmienia polozenia w drzewie. Pierwsza
  redakcja wstawiala wlasny blok obok istniejacej karty i rozjechala bilans
  znacznikow o dwa zamkniecia. Test liczy teraz znaczniki w calej sekcji.

OKNO EDYCJI ILOSCI PODPIETE POD PRZYCISK
* W 3.26.0 okno mialo gotowy rachunek, arkusz i wszystkie stany, ale nie bylo
  podpiete. Klient go nie widzial. Teraz otwiera je przycisk "Zapisz ilosci".
* Okno dostaje metraz, pozycje z ich stanem zamrozenia i pozycje montazowe.
  Decyzja klienta WRACA do widoku wyniku, wiec nie znika po zamknieciu okna.
* DROGA ZAPASOWA. Gdy plik okna sie nie zaladuje, zapis idzie stara droga.
  Brak zasobu nie moze odebrac klientowi mozliwosci zapisania ilosci, ktore
  juz ustawil w stepperach.
* JEDEN punkt zapisu dla obu drog. Dwie kopie tego samego zadania rozjechalyby
  sie przy pierwszej zmianie nazwy pola.
* Okno zamyka sie klawiszem Escape i klikiem w tlo, ma role dialogu i oddaje
  ognisko elementowi, ktory je mial przed otwarciem.
* Widok NIE liczy sam. Kazda kwota pochodzi z czystego rachunku, wiec da sie
  ja odegrac w wierszu polecen bez przegladarki.

DOWODY MUTACYJNE: 18 mutacji, 18 wykrytych
  | Mutacja                                     | Asercji upadlo |
  |---------------------------------------------|----------------|
  | znika lista zakladek                        |              1 |
  | panele startuja widoczne                    |              1 |
  | panel wspolny bez ukrycia                   |              1 |
  | panel bez domkniecia                        |              1 |
  | blok wspolny traci wlasna klase             |              1 |
  | znika znak przy naglowku                    |              1 |
  | licznik gubi limity                         |              1 |
  | licznik PHP ma zle klucze                   |              1 |
  | licznik JS ma zle klucze                    |              1 |
  | zakladka ponizej progu dotyku               |              1 |
  | znika obsluga klawiatury                    |              1 |
  | wybor nie przezywa zapisu                   |              1 |
  | okno nie jest podpiete                      |              1 |
  | dwa punkty zapisu zamiast jednego           |              1 |
  | decyzja klienta nie wraca do widoku         |              1 |
  | widok okna niezalezny od rachunku           |              1 |
  | okno nie zamyka sie klawiszem Escape        |              1 |
  | widok liczy ilosc na wlasna reke            |              2 |

WADA ZLAPANA PRZY PISANIU, ZANIM WESZLA
* Licznik regul w JS mial nazwy kluczy rozwiniete: wykluczone_kategorie zamiast
  wykluczone_kat. Pokazywalby ZERO przy kazdej plaszczyznie, bo zaden klucz by
  nie pasowal, a wyglada to identycznie jak plaszczyzna bez regul. Test czyta
  teraz nazwy kluczy PROSTO ze zrodla PHP i porownuje z lista w JS.

PULAPKA WLASNEGO KOMENTARZA, SZOSTY I SIODMY RAZ
* Sprawdzenie liczby naglowkow "Wykluczone kategorie" naliczylo TRZY zamiast
  dwoch, bo trzecim bylo moje wlasne zdanie w komentarzu, cytujace problem.
* Dowod mutacyjny pokazal to samo w JS: zakomentowanie zapisu wybranej
  zakladki NIE upadalo, bo szukany napis zostawal w komentarzu.
* Oba sprawdzenia czytaja teraz kod z wycietymi komentarzami. To ta sama klasa
  pomylki, co przy relacji o instalatorach i przy sufitach w jednostkach okna.

ASERCJA POSZERZONA, NIE ZLUZOWANA
* Lista zaleznosci widoku wyniku urosla o okno, wiec wzorzec wymagajacy
  DOKLADNIE dwoch wpisow przestal pasowac, choc kolejnosc byla nadal poprawna.
  Wymagane jest teraz: rachunek przed widokiem ORAZ widok od niego zalezny,
  niezaleznie od tego, ile zaleznosci dojdzie pozniej.

CZEGO TA WERSJA NIE ROBI
* Okno ilosci pokazuje pozycje montazowe, gdy widok wyniku je poda. Samo
  wyliczanie akcesoriow na ekranie wyniku, czyli polaczenie przelicznika z
  lista produktow w wizualizacji, nie jest jeszcze podpiete. Dopoki tego nie
  ma, sekcja montazowa w oknie bedzie pusta.
* Sekcja akcesoriow w panelu, przelicznik i rabaty dzialaja i zapisuja sie,
  ale ich wynik nie trafia jeszcze do koszyka WooCommerce.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.26.0 | 3.27.0 |
  |------------------------|--------|--------|
  | zestawow               |     32 |     33 |
  | asercji zaliczonych    |  1 754 |  1 809 |
  | relacji spojnosci      |     40 |     40 |
  | plikow PHP po lincie   |    157 |    157 |
  | plikow JS po lincie    |     50 |     52 |
  | arkuszy CSS            |     25 |     26 |


= 3.26.0 =

PRZELICZNIK AKCESORIOW MONTAZOWYCH
* Zgloszenie: "brakuje wyboru ilosci dla akcesoriow zgodnie z ich
  przelicznikiem, ich ilosc powinna byc wyliczana na podstawie zapotrzebowania.
  Dodaj ustawienia w panelu, przyrost ilosci, np. 1 klej na 8 mb listwy".

  Pomiar przed zmiana
  | Co liczone                        | Przed |
  |-----------------------------------|-------|
  | miejsc liczacych ilosc akcesorium |     0 |
  | ustawien przyrostu w panelu       |     0 |
  | ustawien rabatu w panelu          |     0 |
  Cross-sell byl plaska lista z polem wyboru i cena. PP_Calculator z motywu
  podawal juz package_size, item_length i unit, czyli wydajnosc opakowania.
  Brakowalo drugiej polowy rachunku: ile jednostek podstawy przypada na jedna
  sztuke akcesorium.

* Cztery podstawy liczenia: powierzchnia podlogi, obwod pomieszczenia, liczba
  paczek produktu glownego, stala liczba na zamowienie.

  Zmierzone dla pomieszczenia 22 m2, obwod 18,9 mb, 12 paczek
  | Akcesorium              | Regula          | Sztuk |
  |-------------------------|-----------------|-------|
  | Podklad rolka 6,25 m2   | 1 na 6,25 m2    |     4 |
  | Folia rolka 10 m2       | 1 na 10 m2      |     3 |
  | Listwa sztuka 2,44 m    | 1 na 2,44 mb    |     8 |
  | Klej                    | 1 na 8 mb       |     3 |
  | Zatrzaski               | 1 na 4 paczki   |     3 |
  | Zestaw montazowy        | stale 1         |     1 |
  | bez ustawienia          | brak            |     0 |

* SUFIT, nie zaokraglenie. Pol tubki kleju nie da sie kupic, a brakujaca polowa
  zatrzymuje montaz. Blad w gore kosztuje jedna tubke, blad w dol kosztuje
  wyjazd do sklepu. Zmierzone: 6,26 m2 przy rolce 6,25 daje 2 sztuki, nie 1.
* Regula BEZ USTAWIENIA nie doklada niczego. Milczace domyslne jeden dolozyloby
  po sztuce kazdego akcesorium do kazdego zamowienia i nikt by nie wiedzial,
  skad sie wzielo.
* Minimum i maksimum sztuk per produkt. Maksimum ponizej minimum jest
  ignorowane, bo taka para jest sprzeczna.
* Kazda pozycja niesie zdanie, SKAD wzięła sie ilosc, na przyklad "z obwodu
  18,9 mb, 1 szt. na 8 mb". Klient, ktory nie rozumie, dlaczego w koszyku sa
  trzy tubki kleju, usuwa je wszystkie.

JEDEN RACHUNEK W DWOCH JEZYKACH
* Panel liczy podglad w przegladarce, koszyk liczy prawde w PHP. Rozjazd
  miedzy nimi to inna liczba w podgladzie niz na rachunku, czyli najgorsza
  mozliwa wada tam, gdzie chodzi o pieniadze.
* Zmierzone: 114 przypadkow porownanych wykonaniem, ZERO rozjazdow.

POPUP EDYCJI ILOSCI
* Zgloszenie: "okno z wybranymi elementami, z wybranymi ilosciami, okno do
  wpisania ilosci metrow oraz reczna mozliwosc wpisania dla kazdego z
  produktow. W momencie gdy klient wybiera juz recznie, kalkulator przestaje
  przeliczac reszte, zostawiajac ja na wartosciach z ostatniej zmiany".

  Zamrazanie, odegrane wykonaniem
  | Zdarzenie                      | Panele | Listwa | Listwa scienna |
  |--------------------------------|--------|--------|----------------|
  | metraz 20 m2                   |     12 |     10 |             11 |
  | metraz 30 m2                   |     17 |     14 |             17 |
  | klient wpisuje listwa = 15     |     17 |  15 rc |             17 |
  | metraz 40 m2                   |     23 |  15 rc |             22 |
  | metraz 50 m2                   |     28 |  15 rc |             28 |
  | klient odmraza listwe          |     28 |     23 |             28 |
  rc oznacza pozycje zamrozona przez klienta.

* Zamrozenie dotyczy WYLACZNIE tknietej pozycji. Reszta liczy sie dalej, bo
  klient nie prosil o zamrozenie calego zestawu.
* Jest droga powrotna. Bez niej jedna pomylka zamrazalaby pozycje na zawsze, a
  jedynym wyjsciem byloby zaczecie wizualizacji od nowa.
* Pozycja reczna ma WIDOCZNY znak w oknie. Bez niego klient nie wie, ze wlasnie
  wylaczyl automat, i dziwi sie, czemu ta jedna liczba nie rosnie.
* Zadna z funkcji nie mutuje wejscia. Mutacja listy pozycji dawalaby usterki
  widoczne dopiero po powrocie do okna, czyli najtrudniejsze do powtorzenia.

HIERARCHIA: PRODUKTY GLOWNE I MONTAZ
* Zgloszenie: produkty komplementarne maja byc "relatywnie mniejsze niz glowne,
  ale na tyle przejrzyste, zeby klient wiedzial dlaczego tam sa, do czego sluza
  i ile oddzielnie kosztuja".

  Zmierzone w arkuszu
  | Element            | Pozycja glowna | Pozycja montazowa |
  |--------------------|----------------|-------------------|
  | nazwa              |          15 px |             13 px |
  | miniatura          |          44 px |             34 px |
  | tlo sekcji         |           biale|  wyszarzone #f8fafc |
  Zmniejszenie samego fontu nie wystarcza, wiec montaz dostaje takze mniejsza
  miniature i wlasne tlo sekcji.
* Kazda pozycja montazowa niesie DO CZEGO SLUZY i SKAD wzięła sie ilosc.
* Sumy sa rozdzielone: osobno produkty, osobno montaz, na koncu razem.

RABATY WLASNE, W USTAWIENIACH WIZUALIZATORA
* Kwotowe i procentowe, osobno per produkt i osobno za caly zestaw.
* Rabat NIGDY nie przekracza ceny. Ujemna pozycja w koszyku to nie promocja,
  tylko blad ksiegowy, ktory WooCommerce przyjmie bez slowa. Rabat procentowy
  powyzej stu jest zaciskany do stu.
* Rabat za zestaw liczy sie od kwoty JUZ pomniejszonej o rabaty pozycji.
  Zmierzone na przykladowym zestawie: 121,64 zl zamiast 125,31 zl od ceny
  wyjsciowej. Liczenie od ceny wyjsciowej dawaloby rabat od rabatu, czyli
  kwote wieksza niz obiecana w obu miejscach razem.

STRONA USTAWIEN: TRZY WADY POPRZEDNIEJ FORMY
* Poprzednia strona kategorii dostala ocene "najgorsza forma". Nowa sekcja
  akcesoriow nie powtarza zadnej z trzech rzeczy, ktore ja psuly:
  LISTA ZAMIAST WYSZUKIWARKI  punktem wyjscia jest pole szukania, widoczne sa
                              wylacznie pozycje juz ustawione
  BRAK USUWANIA               kazdy wiersz ma wlasny przycisk 44 px
  USTAWIENIE BEZ SKUTKU       kazdy wiersz pokazuje PODGLAD, ile sztuk wyjdzie
                              przy przykladowym pomieszczeniu, przeliczany przy
                              kazdym nacisnieciu klawisza
* Wiersz dajacy zero sztuk jest WYGASZONY, a nie ukryty. Regula, ktora nic nie
  robi, musi byc widoczna, bo inaczej wlasciciel szuka jej godzinami.
* Produkt usuniety ze sklepu nadal widac jako wiersz z numerem. Ciche
  pominiecie zostawiloby w bazie regule, ktorej nie da sie skasowac.
* Znacznik pustej mapy. Formularz bez ani jednego wiersza nie przysyla klucza
  wcale, wiec bez tego pola skasowanie OSTATNIEJ reguly zostawialoby ja w bazie
  na zawsze. Ta sama wada uniemozliwiala poprzednio usuwanie kategorii.

DOWODY MUTACYJNE: 18 mutacji, 18 wykrytych
  | Mutacja                                     | Asercji upadlo |
  |---------------------------------------------|----------------|
  | zaokraglenie zamiast sufitu                 |          wiele |
  | regula bez ustawien dolicza sztuke          |              4 |
  | rabat moze przekroczyc cene                 |          wiele |
  | rabat zestawu od ceny wyjsciowej            |              2 |
  | granice min i max ignorowane                |          wiele |
  | JS zaokragla, PHP sufituje                  |              1 |
  | zamrozenie przestaje dzialac                |              2 |
  | reczna zmiana nie oznacza pozycji           |              2 |
  | odmrazanie nie przelicza                    |              1 |
  | funkcje mutuja wejscie                      |              1 |
  | sumy nierozdzielone                         |              3 |
  | znika znacznik pustej mapy                  |              1 |
  | oczyszczanie przyjmuje identyfikator zero   |              2 |
  | wyszukiwanie bez sprawdzenia uprawnien      |              1 |
  | arkusz okna wypada z parasola               |              1 |
  | rachunek ladowany PO widoku                 |              1 |
  | montaz tej samej wielkosci co glowne        |              1 |
  | miniatura montazu tej samej wielkosci       |              1 |

DWIE LUKI W TESCIE ZNALEZIONE WLASNYM DOWODEM MUTACYJNYM
* Podmiana domyslnej wartosci "aktywna" na true w pustej regule NIE upadala, bo
  test badal wylacznie JS, a tam pusty obiekt idzie inna galezia. W PHP wartosc
  null wraca cala pusta regule i wtedy domyslna wartosc decyduje o wszystkim.
  Asercja dopisana na stronie PHP, kod nie byl zepsuty.
* Usuniecie bramki na identyfikator zero z petli przelicznikow NIE upadalo, bo
  ten sam wzorzec znajdowal sie jeszcze w petli rabatow. Asercja przechodzila
  dzieki innemu miejscu niz badane. Test liczy teraz wystapienia i sprawdza
  kazda z dwoch petli osobno.

CZEGO TA WERSJA NIE ROBI
* Nie przebudowuje strony ustawien kategorii i wyklucze do powierzchni. Nowa
  sekcja akcesoriow pokazuje, jak ta strona ma wygladac, ale sama sekcja
  kategorii zostaje na razie bez zmian. To osobne wydanie.
* Okno ilosci ma gotowy rachunek, arkusz i wszystkie stany, ale nie jest
  jeszcze podpiete pod przycisk na ekranie wyniku. Podpiecie idzie razem z
  przebudowa tamtej sekcji, zeby nie ruszac jej dwa razy.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.25.1 | 3.26.0 |
  |------------------------|--------|--------|
  | zestawow               |     31 |     32 |
  | asercji zaliczonych    |  1 671 |  1 754 |
  | relacji spojnosci      |     40 |     40 |
  | plikow PHP po lincie   |    155 |    157 |
  | plikow JS po lincie    |     48 |     50 |
  | arkuszy CSS            |     23 |     25 |


= 3.25.1 =

PROGNOZA CZASU USUNIETA
* Zgloszenie: "obliczanie czasu jest jakas abstrakcja, czas oczekiwania
  wzrastal i byl nierealny do rzeczywistosci, wyjeb to".
* Masz racje i to nie jest grzecznosc. Trzy wydania z rzedu probowalem
  powiedziec, ILE zostalo, i trzy razy liczba byla nieprawdziwa:
  3.23.0  licznik liczony z innego odcinka niz mediana
  3.25.0  mediana calosci, ale wagi z jednej obserwacji
  oba     prognoza rosnaca w trakcie czekania zamiast malec
* Czas generowania zalezy od kolejki, obciazenia API i rozmiaru zdjecia, czyli
  od trzech wielkosci, ktorych wtyczka NIE ZNA w chwili startu. Mediana z
  poprzednich przebiegow opisuje przeszlosc, a nie to zadanie. Licznik, ktory
  klamie, jest gorszy niz brak licznika: klient przestaje wierzyc takze
  prawdziwym komunikatom.

  Co bylo, a co jest
  | Element                        | Przed          | Po               |
  |--------------------------------|----------------|------------------|
  | naglowek                       | Zostalo okolo X| nazwa etapu      |
  | podpis                         | z typowych Y   | Czekasz X        |
  | przed pierwszym odczytem       | Zwykle trwa Y  | nazwa etapu      |
  | typical_seconds w odpowiedzi   | zawsze         | USUNIETE         |
  | podloga paska liczona z        | prognozy       | limitu           |

* Zostaja WYLACZNIE fakty: ile juz czekasz, ktory etap trwa, do kiedy czekamy
  najdluzej. Kazdy z szesciu etapow ma wlasna nazwe, wiec ekran mowi, CO sie
  dzieje, a nie tylko ze cos sie dzieje.
* Zmierzone: szesc stanow zadania, zero obietnic czasu w tresci. Test pilnuje
  czterech zakazanych zwrotow: "Zostalo", "typowych", "Zwykle trwa", "okolo".
* Pasek postepu ZOSTAJE bez zmian. On mowi "ile pracy za nami", a nie "ile
  sekund przed toba", i to jest stwierdzenie o stanie, nie prognoza.

ZDJECIE MNIEJSZE O 10 PROCENT
* Zgloszenie: "zdjecie jest lekko za duze, zmniejszyc trzeba o okolo 10%".
* Pierwsza redakcja stawiala sufit max-height: 62vh. POMIAR JA ODRZUCIL: przy
  kontenerze 1410 px, ktory ma panpodloga.pl, obraz ma 668 px, czyli miesci sie
  pod sufitem 670 px i nic nie zostaje przyciete. Sufit w jednostkach okna
  zalezy dodatkowo od jego wysokosci, wiec ta sama strona zmniejszalaby zdjecie
  u jednego klienta, a u drugiego nie.
* Wdrozona redukcja liniowa: scena zajmuje 90 procent szerokosci kolumny.

  Zmierzone w przegladarce, kontener 1410 px, okno 1920x1080
  | Wersja           | Wysokosc obrazu | Redukcja |
  |------------------|-----------------|----------|
  | przed            |          668 px |        0 |
  | po               |          601 px |    10,0% |

* Przy okazji object-fit zmienione z fill na contain. fill rozciagal obraz i
  zmienial proporcje pomieszczenia, ktore klient wlasnie oglada.

BARIERA WARSTW POD SCENA
* Na zrzucie karty akcesoriow nachodzily na zdjecie.

  Trzy hipotezy sprawdzone przyrzadem w przegladarce, WSZYSTKIE OBALONE
  | Hipoteza                                   | Nachodzenie |
  |--------------------------------------------|-------------|
  | uklad w izolacji, bez przewijania          |        0 px |
  | dluga kolumna decyzji, wiersz wyzszy       |        0 px |
  | scena przyklejona, przewiniete 700 px      |        0 px |

* Przyczyny NIE ZNAM. Nie odtwarza sie na samych arkuszach wtyczki, a do kodu
  motywu nie mam dostepu. Zamiast zgadywac czwarta hipoteze postawilem bariere:
  sekcje pod scena maja wlasny kontekst warstw, nieprzezroczyste tlo i wyzszy
  indeks, a scena nizszy. Nachodzenie przestaje byc mozliwe niezaleznie od
  przyczyny.
* Tlo nie jest ozdoba: element bez tla przepuszcza to, co pod nim, wiec sam
  z-index nie zaslonilby nachodzacego obrazu.

DOWODY MUTACYJNE: 10 mutacji, 10 wykrytych
  | Mutacja                                     | Asercji upadlo |
  |---------------------------------------------|----------------|
  | wraca prognoza "Zostalo okolo"              |              3 |
  | wszystkie etapy maja jedna nazwe            |              2 |
  | podloga znowu z prognozy, nie z limitu      |              2 |
  | uplyw znika z fazy koncowej                 |              1 |
  | serwer znowu wysyla czas typowy             |              1 |
  | zdjecie znowu pelnej szerokosci             |              1 |
  | obraz znowu rozciagany                      |              1 |
  | bariera traci kontekst warstw               |              1 |
  | bariera traci tlo                           |              1 |
  | scena wyzej niz bariera                     |              1 |

PIETNASCIE ASERCJI ZMIENIONYCH, NIE NAPRAWIONYCH
* Dwanascie w czekanie.test.js i trzy w skala-paska.test.js wymagalo, zeby
  ekran mowil "Zostalo okolo X" i zeby serwer ZAWSZE wysylal czas typowy.
  Kontrakt zostal swiadomie odwrocony, wiec asercje tez. Nowe pilnuja rzeczy
  przeciwnej: ze prognozy NIE MA, i ze serwer jej nie wysyla. Ukrycie liczby w
  widoku zamiast usuniecia u zrodla zostawiloby ja w odpowiedzi, a pierwsza
  osoba, ktora ja zobaczy, wrocilaby z nia na ekran.

DWIE WADY W TESCIE ZNALEZIONE PRZY PISANIU
* Pomocnik szukajacy reguly CSS bral PIERWSZE wystapienie selektora.
  .pp-rv-result__stage ma cztery bloki, bo osobno wyglada na waskim ekranie, a
  osobno na szerokim. To nie sa duplikaty do scalenia, tylko rozne konteksty,
  wiec test dostal pomocnik szukajacy bloku po zawartej wlasciwosci.
* Sprawdzenie "sufit w jednostkach okna zniknal" zapalilo sie na czerwono na
  WLASNYM komentarzu, ktory cytuje odrzucone max-height: 62vh. Ta sama pulapka,
  co przy relacji o instalatorach. Czyta teraz kod bez komentarzy.

CZEGO TA WERSJA NIE ROBI
* Nie dotyka przelicznika akcesoriow, popupu edycji ilosci, rabatow ani strony
  ustawien kategorii. To trzy osobne funkcje, kazda z wlasnym UI w panelu i we
  froncie, i kazda dostanie wlasne wydanie.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.25.0 | 3.25.1 |
  |------------------------|--------|--------|
  | zestawow               |     31 |     31 |
  | asercji zaliczonych    |  1 661 |  1 671 |
  | relacji spojnosci      |     40 |     40 |
  | plikow PHP po lincie   |    155 |    155 |
  | plikow JS po lincie    |     48 |     48 |
  | arkuszy CSS            |     23 |     23 |


= 3.25.0 =

CZAS OCZEKIWANIA POKAZYWAL 28 s, A TRWALO OKOLO MINUTY
* Zgloszenie: "czas jest zle mierzony, pokazuje 28, trwa to okolo minuty, przy
  czym 3/4 paska pokazalo, ze to prawie koniec, a ostatni etap jest najdluzszy".
* To byly DWIE osobne wady, obie moje.

WADA PIERWSZA: LICZNIK I MEDIANA LICZONE Z ROZNYCH PUNKTOW
* elapsed_seconds liczyl od created_at, czyli od klikniecia klienta.
  typical_seconds bral mediane od generation_started_at, czyli z SAMEGO etapu
  generowania. Na ekranie stalo obok siebie "Czekasz 20 s z typowych 28 s",
  gdzie 20 bylo kawalkiem calosci, a 28 mediana ostatniego etapu.

  Odtworzone na trzech przebiegach
  | Przebieg      | Calosc | Przed | Etap gen | Licznik | Mediana stara |
  |---------------|--------|-------|----------|---------|---------------|
  | typowy        |     60 |    32 |       28 |      60 |            28 |
  | dluga kolejka |     75 |    45 |       30 |      75 |            30 |
  | szybki        |     40 |    18 |       22 |      40 |            22 |

* Licznik przeniosl na created_at ja sam w 3.23.0 i wtedy powstal ten rozjazd:
  druga polowa porownania zostala na starym odcinku. Teraz obie polowy mierza
  CALOSC. Wartosci zapasowe: 60 s calosci, 32 s przed, 28 s generowania.

WADA DRUGA: SKALA PASKA NIE MIALA ZWIAZKU Z CZASEM
* Stala tabela: queued 10, enhancing 40, generating 75, done 100. Najdluzszy
  etap dostawal 25 procent paska, a wejscie w niego SKAKALO na 75.

  Przebieg 60 s, odczyt co 2 s
  | Miara                              | Stara skala | Nowa skala |
  |------------------------------------|-------------|------------|
  | odczytow bez zmiany szerokosci     |     27 z 31 |     1 z 31 |
  | pasek na starcie najdluzszego etapu|         75% |        53% |
  | szerokosc paska na ten etap        |         25% |        37% |

* Granica miedzy czescia przed generowaniem a generowaniem wynika teraz z
  POMIARU obu odcinkow na danej instalacji, a nie z tabeli wpisanej z reki.
  Wewnatrz etapu pasek pelznie wedlug uplywu, wiec nie stoi nawet wtedy, gdy
  status sie nie zmienia.

  Granica przy roznych podzialach, zmierzona
  | Przed | Generowanie | Granica |
  |-------|-------------|---------|
  |    32 |          28 |     53% |
  |    10 |          50 |     25% |
  |    50 |          10 |     70% |
  |     0 |           0 |     50% |

* Niezmienniki sprawdzone wykonaniem na 300 odczytach: zero cofniec, maksimum
  97 procent przed statusem done. Pasek dobijajacy do stu przed koncem klamie
  tak samo jak pasek stojacy w polowie.

PODLOGA PASKA PRZESTALA BYC DRUGIM ZRODLEM SZEROKOSCI
* Klient liczyl wlasna podloge z czasu, niezaleznie od procentu z serwera.
  Przy starym czasie typowym przebijala serwer w 9 odczytach na 10 i dobijala
  do 92 procent na trzydziestej sekundzie, czyli w polowie oczekiwania. To
  druga polowa tego, co wlasciciel opisal jako "3/4 paska".
* Podloga moze teraz wyprzedzic serwer najwyzej o 5 punktow. Tyle wystarczy,
  zeby pasek pelznl przez jedno zgubione odpytanie, i za malo, zeby dobiegl do
  konca na wlasna reke. Zmierzone najwieksze przebicie po zmianie: 1 punkt.

EKRAN WYNIKU: 4734 px, CZYLI 4,4 EKRANU
* Zgloszenie: "nie rob tego scrollowania tak, informacje maja byc czytelne,
  kompaktowe, czyste, wzoruj sie na panpodloga.pl/inspiracje/".
* Zmierzone na zrzucie z produkcji przy oknie 1920x1080: 4734 px. Galeria
  WSZYSTKICH zapisanych wizualizacji stala pod wynikiem jako nieograniczona
  siatka czterokolumnowa i rosla z kazda kolejna praca.

  Warianty zmierzone przyrzadem w przegladarce (10 kafli, okno 1920x1080)
  | Wariant                             | Wysokosc | Ekranow | 390 px |
  |-------------------------------------|----------|---------|--------|
  | A baza, siatka czterokolumnowa      |  2359 px |    2,18 |   4,11 |
  | B pasek zamiast siatki              |  1597 px |    1,48 |   2,76 |
  | C pasek + kompaktowe akcesoria      |  1340 px |    1,24 |   2,11 |
  | D wszystko za zakladkami            |  1080 px |    1,00 |   1,55 |
  | E akcesoria widoczne, galeria pasek |  1340 px |    1,24 |   2,11 |
  | N kontrola: same mniejsze odstepy   |  2257 px |    2,09 |   4,03 |

* WDROZONY WARIANT C.
* ODRZUCONE i dlaczego:
  N, kontrola ujemna: zmniejszenie WSZYSTKICH odstepow o 40 procent dalo
     4 procent mniej wysokosci. Zysk nie bierze sie ze sciskania.
  E: co do piksela rowny C, wiec nie wnosi nic poza kolejna sciezka kodu.
  D: wygrywa wysokoscia, ale chowa akcesoria i galerie za klinieciem. Chowa
     wiec sprzedaz dodatkowa i lamie prosbe o czytelnosc informacji.
* Po zmianie zmierzono: 0 celow dotykowych ponizej 44 px na obu progach,
  11 z 11 sprawdzen kontrastu zaliczonych, jedna dominanta akcentowa
  (koszyk 19200 px2 wobec 3269 px2 drugiego przycisku).

CO DOKLADNIE SIE ZMIENILO NA EKRANIE WYNIKU
* Galeria pod wynikiem to jeden rzad z przewijaniem poziomym i zatrzymywaniem
  na kaflu, z odnosnikiem do pelnej galerii. Pasek przewijania zostaje
  widoczny, bo ukryty zabiera jedyna wskazowke, ze da sie przewinac dalej.
* Zakres reguly ograniczony do sekcji pod wynikiem. Pelna galeria na koncie
  pozostaje siatka: ten sam wyglad w dwoch rolach bylby bledem.
* Akcesoria z ukladu pionowego na poziomy. Obraz 1:1 przy czterech kolumnach
  mial okolo 250 px wysokosci, czyli cztery pozycje dodatkowe zjadaly cwierc
  ekranu. Tresc bez zmian: obraz, nazwa, cena, odnosnik, przycisk powiekszenia.
* Odnosnik "Pelna galeria" mial 87x19 px i byl JEDYNYM celem ponizej progu
  44 px. Ma teraz pelny wymiar.

TOKENY ZMIERZONE, NIE WYMYSLONE
* Wartosci wziete z arkusza panpodloga.pl/inspiracje/: akcent #059669
  (hover #047857), obramowanie #e2e8f0, tekst pomocniczy #64748b, promien
  16 px, cien 0 8px 30px rgba(0,0,0,.03), cien po najechaniu
  0 15px 35px rgba(15,23,42,.15), proporcja kafla 4/3, etykieta 10 px/700
  wielkimi literami z odstepem 1 px w pigulce 30 px.
* Silnik ui-ux-pro-max-engine zaproponowal palete cieplego betonu i debu
  (#3A3127, akcent #D97706, tlo #FAF9F6). ODRZUCONA: marka ma zmierzona
  zielen szmaragdowa i granat, a prosba brzmiala "wzoruj sie na inspiracjach",
  czyli na wlasnej stronie, nie na korpusie stylow. Ze wskazan silnika przyjete
  zostaly WYLACZNIE strukturalne: wezwanie nad zgieciem, trwale podsumowanie
  zamowienia, brak samoczynnych karuzeli, kontrast 4,5:1, poszanowanie
  prefers-reduced-motion. Test pilnuje, ze odrzucona paleta nie wroci.

DOWODY MUTACYJNE: 18 mutacji, 18 wykrytych
  | Mutacja                                          | Asercji upadlo |
  |--------------------------------------------------|----------------|
  | granica wraca do stalej 75                       |              5 |
  | pasek nie pelznie w etapie                       |              5 |
  | ulamek moze dobic do jedynki                     |              3 |
  | ulamek nie rosnie liniowo                        |              3 |
  | sufit podniesiony do stu                         |              2 |
  | czas typowy wraca do mediany etapu               |              2 |
  | mediana calosci liczy zly odcinek                |              1 |
  | nazwa kolumny bez bialej listy                   |              2 |
  | wartosc zapasowa czasu wraca do 45               |              1 |
  | procent nie dostaje rekordu zadania              |              1 |
  | znika zacisk podlogi                             |              3 |
  | luz podlogi rozdmuchany                          |              3 |
  | galeria wraca do siatki                          |              1 |
  | pasek traci zakres, obejmuje pelna galerie       |              4 |
  | odnosnik traci cel dotykowy                      |              1 |
  | akcesoria wracaja do pionu                       |              1 |
  | miniatura znowu rosnie z kolumna                 |              2 |
  | dluga nazwa nie jest przycinana                  |              3 |

TRZY LUKI W TESCIE ZNALEZIONE WLASNYM DOWODEM MUTACYJNYM
* Podmiana galezi asymptotycznej ulamka na 1.0 nie upadala w zadnym z 38
  sprawdzen, bo procent() zaciska wynik sufitem i roznica byla z zewnatrz
  niewidoczna. Kontrakt istnial w komentarzu, nie w tescie. Dopisano dowod na
  jedenastu punktach: zero w zerze, 0,9 w czasie oczekiwanym, monotonicznosc,
  brak jedynki nawet po tysiackrotnym przekroczeniu.
* Usuniecie bialej listy nazw kolumn nie upadalo, bo metoda jest prywatna i
  zaden test jej nie wolal. Test siega po nia refleksja i podaje nazwe z
  fragmentem SQL: odrzucenie nastepuje PRZED zapytaniem.
* Sprawdzenie przyciecia nazwy szukalo napisu "line-clamp: 2", ktory jest
  PODCIAGIEM "-webkit-line-clamp: 2", wiec popsucie wersji bez przedrostka
  przechodzilo. Ta sama pulapka podciagu zlapala mnie w tym projekcie po raz
  piaty. Sprawdzane sa teraz obie pisownie osobno i ich zgodnosc.

DWIE WADY ZNALEZIONE PRZY OKAZJI, OBIE MOJE
* Reguly ukladu akcesoriow dopisalem najpierw na koncu arkusza zamiast scalic
  z pierwotnymi. Kaskada dawala poprawny wynik, ale w jednym pliku stalyby dwa
  bloki tej samej klasy, czyli ta sama wada, co dwa zrodla prawdy. Test czyta
  PIERWSZE wystapienie i to go wylapalo. Sprawdzenie obejmuje teraz cala
  rodzine pieciu klas, nie sama karte.
* Przy scalaniu usunalem blok razem z klamrami i zostawilem sama liste
  selektorow zakonczona przecinkiem. Bilans klamer pozostal rowny, a arkusz byl
  zepsuty: osierocona lista polykala nastepna regule i nakladala jej style na
  dwie obce klasy. Zliczanie nawiasow to nie sprawdzenie skladni. Dopisano
  kontrole osieroconych list selektorow.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.24.2 | 3.25.0 |
  |------------------------|--------|--------|
  | zestawow               |     29 |     31 |
  | asercji zaliczonych    |  1 555 |  1 661 |
  | relacji spojnosci      |     40 |     40 |
  | plikow PHP po lincie   |    154 |    155 |
  | plikow JS po lincie    |     46 |     48 |
  | arkuszy CSS sprawdzonych |    0 |     23 |


= 3.24.2 =

GENEROWANIE STAWALO NA ANALIZIE ZDJECIA
* Zgloszenie z ekranu klienta:
  "Model gemini-3.5-flash zwrocil odpowiedz, ktorej nie udalo sie odczytac.
   Sprawdz w ustawieniach, czy wybrany model jest dostepny na Twoim koncie API."
* Komunikat byl bledna diagnoza. Niedostepny model daje 404 z bramki, czyli
  zupelnie inna sciezke. Tutaj model odpowiadal, tylko wtyczka nie umiala
  odczytac odpowiedzi ani powiedziec, dlaczego.

  Pomiar w calym katalogu includes
  | Co liczone                                        | Przed | Po |
  |---------------------------------------------------|-------|----|
  | miejsc czytajacych candidates[0].parts[0].text    |    24 |  0 |
  | miejsc czytajacych finishReason                   |     0 | 21 |
  | miejsc czytajacych promptFeedback.blockReason     |     0 | 21 |
  | miejsc rozpoznajacych czesci typu thought         |     0 | 21 |
  | maxOutputTokens dla analizy zdjecia               |  1024 | 4096 |

TRZY POWODY, DLA KTORYCH parts[0].text BYWA PUSTE
* Czesc typu thought. Modele mysace zwracaja rozumowanie jako osobna czesc.
  Gdy jest PIERWSZA, parts[0] zawiera proze rozumowania zamiast JSON-a.
* finishReason rowne MAX_TOKENS. Budzet wyjscia wyczerpuje sie na rozumowaniu
  i czesc z odpowiedzia nie powstaje wcale.
* promptFeedback.blockReason. Odpowiedz zablokowana filtrem tresci.

  Zmierzone na osmiu ksztaltach odpowiedzi
  | Ksztalt                          | Stary odczyt | Nowy odczyt |
  |----------------------------------|--------------|-------------|
  | zwykla odpowiedz                 | tekst        | TEKST       |
  | rozumowanie jako pierwsza czesc  | PROZA        | JSON        |
  | odpowiedz w dwoch czesciach      | polowa       | CALOSC      |
  | limit dlugosci, brak tresci      | pusto        | pusto       |
  | limit dlugosci, samo rozumowanie | PROZA        | pusto       |
  | blokada filtra tresci            | pusto        | pusto       |
  | brak kandydatow                  | pusto        | pusto       |
  | cialo nie jest tablica           | pusto        | pusto       |
  Trzeci i drugi wiersz to roznica miedzy dzialajaca analiza a komunikatem
  o bledzie: proza rozumowania nie przechodzi przez json_decode.

JEDNO MIEJSCE ZAMIAST DWUDZIESTU CZTERECH
* Nowa klasa PP_Insp_Gemini_Odpowiedz. Sklada tekst ze WSZYSTKICH czesci
  wszystkich kandydatow, pomijajac czesci rozumowania. Czyta obie pisownie
  danych obrazowych, inline_data oraz inlineData.
* powod() mowi, co sie stalo, slowami odpowiedzi API: blokada filtra, limit
  dlugosci, brak kandydatow. Zaden komunikat nie odsyla juz do dostepnosci
  modelu na koncie, bo to nie byla przyczyna.
* Wywolan wspolnego odczytu: 25 w pieciu plikach.

MARTWA GALAZ: AUTOMATYCZNE PRZEJSCIE NA MODEL AWARYJNY
* Petla ponowienia pomija pozycje rowna modelowi glownemu. Lancuch roli
  wizyjnej skladal sie z JEDNEGO modelu, ktory byl rownoczesnie glownym.

  Proby ponowienia, zmierzone
  | Rola   | Lancuch przed              | Prob przed | Lancuch po | Prob po |
  |--------|----------------------------|------------|------------|---------|
  | text   | 3.5-flash, 3-flash-preview |          1 | 4 pozycje  |       3 |
  | vision | 3.5-flash                  |          0 | 3 pozycje  |       2 |
  | image  | 3-pro-image, 3.1-flash-img |          1 | 2 pozycje  |       1 |

* Rola wizyjna z pustym ustawieniem dziedziczy model tekstowy, a jej jedyny
  fallback to ten SAM model. Przy ustawieniach fabrycznych cialo petli nie
  wykonywalo sie ANI RAZU. Sprawdzone dla wszystkich czterech dozwolonych
  wyborow administratora, nie tylko domyslnego: teraz kazdy ma alternatywe.
* Lancuch domykany jest lista dozwolonych i odsiewa modele oznaczone jako
  niedostepne po 404, z wyjatkiem pozycji glownej, bo to jej awarie trzeba
  pokazac.

PONOWIENIE OBEJMOWALO ZLY MOMENT
* Blok ponowienia stal PRZED parsowaniem JSON, wiec odpowiedz 200 bez tresci
  nie uruchamiala go wcale. Dopisany kod bledu pusta_odpowiedz.
* Urwanie na limicie dlugosci ma teraz wlasna reakcje: powtorzenie z budzetem
  czterokrotnie wiekszym. Przejscie na inny model nic by nie dalo, bo problemem
  nie jest model. Ponowienie tym samym budzetem dalo by ten sam wynik i tylko
  podwoilo czas oczekiwania klienta.
* Budzet jest ustawieniem: Limit dlugosci odpowiedzi modelu, domyslnie 4096,
  ograniczony z obu stron do przedzialu 512 do 32768.

HALAS W DZIENNIKU BLEDOW PRZY KAZDEJ AKTYWACJI
* Z logu:
  ALTER TABLE wp_pp_rv_jobs ADD COLUMN /*
  ALTER TABLE wp_pp_rv_jobs ADD COLUMN * generating i ZEROWANA przy ponowieniu
  ALTER TABLE wp_pp_rv_gallery ADD COLUMN * parsowania w linii 94.
* Przyczyna moja: komentarze PHP stały WEWNATRZ literalu CREATE TABLE. dbDelta
  parsuje cialo definicji linia po linii i kazda niepusta linie traktuje jak
  definicje kolumny, wiec pierwszy token linii komentarza staje sie nazwa
  kolumny. Linie z gwiazdka zwijaly sie do jednej, bo dbDelta kluczuje pola po
  nazwie i wygrywala ostatnia. Stad dokladnie trzy bledne zapytania na tabele.
* Zmierzone: 6 bednych zapytan przy kazdej aktywacji, 2 tabele. Same kolumny
  powstawaly poprawnie, wiec objawem byl WYLACZNIE halas. Halas maskujacy
  prawdziwe bledy bazy jest jednak tym, co utrudnia diagnoze nastepnej usterki.
* Opisy kolumn przeniesione nad metode. Po zmianie: 0 linii komentarza w 16
  literalach CREATE TABLE.

TRZY NOWE RELACJE SPOJNOSCI (37 -> 40)
* Definicje CREATE TABLE nie zawieraja komentarzy, z licznikiem niewaznosci.
* Odpowiedzi Gemini czytane wspolnym odczytem, czyli zero surowych
  parts[0][text] i co najmniej 15 uzyc wspolnej klasy.
* Kazda rola modelu ma model awaryjny inny niz glowny, liczona ta sama petla,
  co w potoku.
* Runner autotestu dolacza teraz aktywator i rejestr modeli. Relacja opakowana
  w class_exists przy niezaladowanej klasie NIE zglasza bledu, tylko nie
  powstaje wcale, a licznik zaliczonych i tak rosnie. Zestaw wygladalby na
  zielony, nie zbadawszy tego, po co powstal.

DOWODY MUTACYJNE: 16 mutacji, 16 wykrytych
  | Mutacja                                              | Asercji upadlo |
  |------------------------------------------------------|----------------|
  | odczyt wraca do parts[0]                             |              4 |
  | czesc rozumowania nie jest pomijana                  |              3 |
  | brany tylko pierwszy kawalek tekstu                  |              1 |
  | powod ignoruje blokade filtra                        |              2 |
  | powod ignoruje MAX_TOKENS                            |              1 |
  | wraca mylacy komunikat o koncie API                  |              2 |
  | urwana_na_limicie zawsze falsz                       |              1 |
  | lancuch znowu bez alternatywy                        |              4 |
  | budzet tokenow znowu na sztywno 1024                 |              1 |
  | warunek pustej odpowiedzi wylaczony                  |              1 |
  | warunek ponowienia z wiekszym budzetem wylaczony     |              1 |
  | ponowienie nie obejmuje pustej odpowiedzi            |              1 |
  | komentarz wraca do CREATE TABLE                      |              2 |
  | znika dolna granica budzetu                          |              1 |
  | znika pole budzetu w panelu                          |              2 |
  | granice pola rozjezdzaja sie z kodem                 |              1 |

LUKA W TESCIE ZNALEZIONA WLASNYM DOWODEM MUTACYJNYM
* Podmiana warunku if ( pusty tekst ) na if ( false ) NIE zostala wykryta:
  asercje sprawdzaly obecnosc kodu bledu i wywolania powodu, a nie to, czy
  warunek prowadzacy do nich jest zywy. Caly blok zostawal nietkniety i martwy.
  Asercja dopisana, kod nie byl zepsuty.

CZEGO TA WERSJA NIE DOTYKA
* W dzienniku sa takze proby wstrzykniecia SQL przez wp-json, w rodzaju
  UN/**/ION SEL/**/ECT oraz EXTRACTVALUE, idace przez WP_REST_Server
  serve_batch_request_v1 do WP_REST_Posts_Controller. To ruch z zewnatrz
  przeciwko rdzeniowi WordPressa, nie przeciwko tej wtyczce, i baza je
  odrzucila. Wtyczka nie ma jak ich zatrzymac.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.24.1 | 3.24.2 |
  |------------------------|--------|--------|
  | zestawow               |     28 |     29 |
  | asercji zaliczonych    |  1 486 |  1 555 |
  | relacji spojnosci      |     37 |     40 |
  | plikow PHP po lincie   |    152 |    154 |
  | plikow JS po lincie    |     46 |     46 |


= 3.24.1 =

BLAD KRYTYCZNY PRZY AKTYWACJI WTYCZKI
* Zgloszenie z produkcji:
  Uncaught Error: Class "PP_Insp_Event_Ledger" not found
  in .../pp-inspirations/pp-inspirations.php on line 496
  do_action('activate_pp-inspirations/pp-inspirations.php')

* ODTWORZONE W PIASKOWNICY, ten sam numer linii i ten sam komunikat. Nie
  wnioskowanie o przyczynie, tylko jej wywolanie.

PRZYCZYNA, ZMIERZONA
* Aktywacja z ekranu Wtyczki przebiega tak, ze plugins_loaded ODPALIL SIE JUZ
  WCZESNIEJ, gdy wtyczka byla nieaktywna. Instancja glowna wisi wlasnie na
  plugins_loaded, wiec jej metoda includes() nie wykonuje sie wcale.

  Stan w chwili wywolania hakow aktywacji
  | Miara                                   | Wartosc |
  |-----------------------------------------|---------|
  | zaladowanych klas PP_                   |       2 |
  | PP_Insp_Event_Ledger istnieje           |     NIE |
  | wolan PP_Insp_Event_Ledger::install()   |       1 |
  | oslon class_exists wokol tego wolania   |       0 |

* Domkniecie aktywacji dolaczalo cztery pliki i wolalo instalator na piatej
  klasie, ktorej nikt nie dolaczyl.

DRUGIE DNO, CICHSZE
* Po zakomentowaniu samej linii 496 blad krytyczny znikal. Pomiar pokazal, ze
  to zamienilo by awarie glosna na cicha.

  Ktore tabele powstawaly przy aktywacji, przed naprawa
  | Tabela            | Wlasciciel           | Wynik |
  |-------------------|----------------------|-------|
  | pp_insp_events    | PP_Insp_Event_Ledger | BRAK  |
  | pp_insp_stats     | PP_Insp_Attribution  | BRAK  |
  | pp_insp_carts     | PP_Insp_Cart_Store   | BRAK  |
  | pp_insp_push_subs | PP_Insp_Push         | BRAK  |
  wywolan dbDelta 7, brakujacych tabel 4

* Siedem wywolan dbDelta dotyczylo analityki i wizualizatora, czyli klas,
  ktorych pliki domkniecie faktycznie dolaczalo. Zadna z czterech tabel
  warstwy growth nie powstawala przy aktywacji NIGDY, bo trzy instalatory
  stoja za class_exists() i przy niezaladowanym pliku po prostu milcza.
* To wyjasnia wpis z dziennika produkcji sprzed kilku wydan:
  Table 'cgfhorof.wp_pp_insp_events' doesn't exist.

  Po naprawie
  | Tabela            | Wynik     |
  |-------------------|-----------|
  | pp_insp_events    | utworzona |
  | pp_insp_stats     | utworzona |
  | pp_insp_carts     | utworzona |
  | pp_insp_push_subs | utworzona |
  wywolan dbDelta 10, brakujacych tabel 0

JEDEN TOR ZAMIAST TRZECH MIEJSC
* Nowa klasa PP_Insp_Activator. Lista wlascicieli tabel byla dotad w trzech
  miejscach pliku glownego: dwa razy jako literal petli i raz jako gole
  wywolanie. Kazde mozna bylo poprawic osobno i rozjechac osobno.
* Teraz jest jedna tablica: klasa, plik, nazwa tabeli. Aktywator sam dolacza
  pliki i po instalacji SPRAWDZA przez SHOW TABLES, czy tabela istnieje.
  Instalator, ktory cicho nic nie zrobil, wyglada z zewnatrz identycznie jak
  instalator, ktory zadzialal, wiec sprawdzany jest skutek, a nie wywolanie.
* Kazdy wlasciciel jest izolowany try/catch. Aktywacja, ktora sie wywala,
  blokuje ekran Wtyczki, wiec awaria jednego nie zabiera pozostalych trzech.

HAKI AKTYWACJI NIE ODPALAJA SIE PRZY AKTUALIZACJI
* WordPress uruchamia register_activation_hook przy WLACZANIU wtyczki, a nie
  przy wgraniu nowszej paczki. Kto od wersji do wersji tylko podmienia ZIP,
  nie dostalby instalacji tabel ani razu, a naprawa dzialalaby wylacznie u
  kogos, kto wpadnie na pomysl wylaczenia i wlaczenia wtyczki.
* Dlatego aktywator wisi takze na admin_init i przy zmianie numeru wersji
  zaklada brakujace tabele sam. Priorytet 5, czyli PRZED autotestem
  spojnosci na 20: autotest ma widziec stan PO probie naprawy, inaczej
  zglaszalby brak tabel, ktore zaraz potem powstaja, a ostrzezenie znikajace
  bez powodu uczy ignorowania ostrzezen.
* Ponawianie ograniczone do razu na godzine. Przy trwalej przyczynie, na
  przyklad braku uprawnienia CREATE, warunek jest falszywy przy KAZDYM
  wejsciu na dowolna strone zaplecza, wiec bez ogranicznika kazde odswiezenie
  panelu placiloby czterema zapytaniami i czterema dbDelta bez skutku.

ASERCJA ZMIENIONA, NIE NAPRAWIONA
* Relacja spojnosci "Kazdy wlasciciel tabeli jest wolany przy aktywacji"
  sprawdzala strpos( plik_glowny, nazwa_klasy ), czyli czy nazwa WYSTEPUJE
  gdziekolwiek w pliku. Przechodzila na zielono przez cztery wydania, podczas
  gdy nie powstawala ani jedna z czterech tabel. Mierzyla wlasny tekst.
* W jej miejsce trzy sprawdzenia kodu plus jedno bazy:
  - kazdy wlasciciel tabeli ZNALEZIONY W KODZIE jest w mapie aktywatora,
  - kazda sciezka z mapy wskazuje istniejacy plik,
  - zaden instalator nie jest wolany bezposrednio w haku aktywacji,
  - RUNTIME: tabele wlascicieli faktycznie istnieja w bazie.
  Ostatnie czyta baze, bo kod moze byc poprawny, a tabeli i tak moze nie byc.
  Bez tego jedynym objawem jest wpis w dzienniku serwera przy zadaniu crona.
* Relacji: 35 -> 37.

POMOCNIK: KOD BEZ KOMENTARZY
* Pierwsza wersja sprawdzenia "zaden instalator nie jest wolany bezposrednio"
  zapalila sie na czerwono na WLASNYM komentarzu, ktory cytuje usunieta linie
  PP_Insp_Event_Ledger::install(). Sprawdzenie statyczne czytajace komentarze
  zglasza usterke opisana slowami zamiast usterki istniejacej w kodzie. Ta
  klasa pomylek wraca, wiec ma teraz jedno miejsce:
  PP_Insp_Coherence_Selftest::bez_komentarzy().

DOWODY MUTACYJNE: 14 mutacji, 14 wykrytych
  | Mutacja                                             | Asercji upadlo |
  |-----------------------------------------------------|----------------|
  | znika dolaczenie aktywatora na poziomie pliku       |             11 |
  | wraca goly instalator w haku aktywacji              |             20 |
  | z mapy wypada jeden wlasciciel                      |             10 |
  | zaladuj przestaje dolaczac pliki                    |             24 |
  | wpis mapy wskazuje nieistniejacy plik               |             15 |
  | uruchom nie sprawdza skutku, tylko fakt wywolania   |              4 |
  | drugi przebieg liczy istniejace jako nowo zalozone  |              3 |
  | samonaprawa nigdy sie nie uruchamia                 |              2 |
  | samonaprawa startuje PO autotescie spojnosci        |              1 |
  | brakujace_tabele zawsze mowi, ze jest komplet       |              1 |
  | wyciecie komentarzy przestaje dzialac               |              3 |
  | znika ogranicznik ponawiania                        |              1 |
  | ogranicznik nigdy nie wygasa                        |              1 |
  | aktywator nie startuje wcale                        |              2 |
  Mutacja druga odtwarza dokladnie komunikat ze zgloszenia.

DWIE LUKI W TESCIE ZNALEZIONE WLASNYM DOWODEM MUTACYJNYM
* Podmiana sprawdzenia skutku na if ( true ) NIE zostala wykryta przez zaden
  z 62 testow: nie bylo przypadku, w ktorym instalator przechodzi, a tabela
  nie powstaje. Atrapa dostala tryb gluchy, ktory odwzorowuje dbDelta bez
  bledu i bez tabeli. Asercja dopisana, kod nie byl zepsuty.
* Sprawdzenie startu aktywatora czytalo surowe zrodlo, wiec zakomentowanie
  // PP_Insp_Activator::init(); przechodzilo: zakomentowana linia nadal
  zawiera ten napis. Czyta teraz kod bez komentarzy.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.24.0 | 3.24.1 |
  |------------------------|--------|--------|
  | zestawow               |     27 |     28 |
  | asercji zaliczonych    |  1 414 |  1 486 |
  | relacji spojnosci      |     35 |     37 |
  | plikow PHP po lincie   |    149 |    152 |
  | plikow JS po lincie    |     46 |     46 |


= 3.24.0 =

NIEZATWIERDZONE INSPIRACJE SZLY DO FEEDU
* Zgloszenie: "napraw, aby niezatwierdzone inspiracje nie szly do feeda".
* Pomiar: klasa feedu miala ZERO wystapien pojecia zatwierdzenia.

  Bramki w sync() przed zmiana, wszystkie techniczne
  | Bramka                     | Co sprawdzala          |
  |----------------------------|------------------------|
  | WooCommerce aktywne        | srodowisko             |
  | typ wpisu                  | czy to inspiracja      |
  | sklad                      | czy ma produkty        |
  | obrazek wyrozniajacy       | wymog Merchant Center  |
  | minimum pozycji            | zestaw, nie pojedynczy |
  | cena wieksza od zera       | poprawnosc oferty      |
  Ani jednej bramki o tym, czy wlasciciel to widzial.

* Flaga _pp_gmc_approved ISTNIALA, ale decydowala WYLACZNIE o tym, czy uzyc
  skomponowanego tytulu i opisu. O tym, czy oferta ma w ogole istniec, nie
  decydowalo nic.

BRAMKA W JEDYNYM LEJKU
* sync() woła szesc roznych miejsc: zapis inspiracji, przywrocenie z kosza,
  zmiana komponentu, kontrola cen, synchronizacja wsadowa i panel GMC.
  Bramka stoi w sync(), a nie w kazdym z nich, bo szesc kopii tej samej
  reguly rozjechaloby sie przy siodmym wywolaniu.
* Stoi PRZED liczeniem skladu i ceny, wiec niezatwierdzona inspiracja nie
  kosztuje nawet zapytan do bazy.

WYCOFUJE, A NIE TYLKO POMIJA
* Sama odmowa tworzenia zostawilaby w feedzie wszystko, co juz tam jest,
  czyli nie rozwiazalaby zgloszenia. Bramka wola unpublish_shadow(), wiec
  oferta znika z Merchant Center przy najblizszej synchronizacji.

  Tablica decyzji, uruchomiona
  | Bramka    | Zatwierdzona | Idzie do feedu |
  |-----------|--------------|----------------|
  | wlaczona  | tak          | tak            |
  | wlaczona  | nie          | NIE            |
  | wylaczona | tak          | tak            |
  | wylaczona | nie          | tak            |

* Zatwierdza WYLACZNIE wartosc '1'. Zmierzone: '0', pusty lancuch, 'tak',
  'yes', liczba 2 i 'true' NIE zatwierdzaja. Luzniejszy warunek wpuscilby
  do feedu wszystko, co ma cokolwiek w tej meta.

WLACZONA DOMYSLNIE, ALE ZE SKALA NA WIERZCHU
* Domyslnie WLACZONA, bo o to bylo zgloszenie. Bramka wylaczona domyslnie
  nie naprawilaby niczego, dopoki wlasciciel by jej nie znalazl.
* Wycofanie ofert z Merchant Center widac w wynikach wyszukiwania, a nie
  w logu wtyczki, wiec ekran "Nazwy i opisy GMC" i ustawienia wizualizatora
  pokazuja LICZBY: ile inspiracji ma produkt-cien i ile z nich nie jest
  zatwierdzonych. Wlasciciel widzi skale, zanim cokolwiek zniknie.
* Gdy bramka jest wylaczona, panel mowi to wprost, czerwonym zdaniem.
  Wylaczony straznik, ktory o sobie nie przypomina, jest gorszy niz brak
  straznika.
* Zatwierdzanie zbiorcze juz istnialo na tym ekranie: zaznacz i zatwierdz.

PRZELACZNIK, KTORY REALNIE SIE ZAPISUJE
* Pole ukryte z zerem stoi PRZED przelacznikiem. Odznaczony checkbox nie
  jest wysylany wcale, wiec bez tego zapis zaleznie od wersji WordPressa
  zostawialby stara wartosc albo zapisywal null. Wylaczenie bramki to
  decyzja o pieniadzach i nie moze zalezec od tego, jak zachowa sie
  options.php.

NOWA RELACJA SPOJNOSCI (34 -> 35)
* Klucz zatwierdzenia jest zdefiniowany DWA razy: w klasie feedu i w panelu.
  Feed nie siega po stala panelu celowo, bo musi dzialac takze wtedy, gdy
  panel nie jest zaladowany, a twarda zaleznosc dawalaby blad krytyczny.
  Rozjazd tych stalych oznaczalby, ze zatwierdzenie w panelu nie otwiera
  bramki, a nic by tego nie zglosilo: oferty po prostu znikalyby z feedu.
  Relacja pilnuje, ze obie maja te sama wartosc, i ma licznik niewaznosci.

DOWODY MUTACYJNE
  | Mutacja                                        | Asercji upadlo |
  |------------------------------------------------|----------------|
  | bramka domyslnie wylaczona                     |              1 |
  | bramka nie blokuje niczego                     |              2 |
  | bramka pomija, ale NIE wycofuje z feedu        |              1 |
  | dowolna wartosc meta zatwierdza                |              5 |
  | klucz zatwierdzenia rozjezdza sie z panelem    |              1 |
  | znika licznik skali                            |              1 |
  | licznik liczy takze szkice                     |              1 |
  | panel przestaje pokazywac stan bramki          |              1 |
  | znika ostrzezenie przy wylaczonej bramce       |              1 |
  | znika pole ukryte przelacznika                 |              1 |
  | R35: rozjazd kluczy                            |              1 |
  | R35: wzorzec przestaje pasowac                 |              1 |

LUKA W TESCIE ZNALEZIONA WLASNYM DOWODEM MUTACYJNYM
* Sprawdzenie licznika szukalo napisu 'function policz_niezatwierdzone'.
  Zmiana nazwy metody na 'policz_niezatwierdzone_NIEMA' przechodzila, bo to
  jego podciag, a panel wolalby wtedy metode, ktorej nie ma. Asercja
  porownuje teraz pelna sygnature z nawiasem i sprawdza dodatkowo, ze panel
  wola DOKLADNIE te metode, ktora zdefiniowano.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.23.0 | 3.24.0 |
  |------------------------|--------|--------|
  | zestawow               |     26 |     27 |
  | asercji zaliczonych    |  1 367 |  1 414 |
  | relacji spojnosci      |     34 |     35 |
  | plikow PHP po lincie   |    148 |    149 |
  | plikow JS po lincie    |     46 |     46 |


= 3.23.0 =

EKRAN OCZEKIWANIA NIE MOWIL NIC O CZASIE
* Zgloszenie: "pasek stoi dlugo w miejscu, nie wiadomo, czy to dziala, nie
  wiadomo, kiedy sie konczy. To musi byc informacja, ILE ma czekac, a nie
  czy sie cos zepsulo."
* TRZY przyczyny, wszystkie zmierzone w zrodle:

  | Co                        | Dlaczego bylo puste                        |
  |---------------------------|--------------------------------------------|
  | uplyw czasu               | liczony od generation_started_at, a ten stempel powstaje dopiero przy statusie generating i jest ZEROWANY przy powrocie do queued |
  | "zwykle X"                | mediana zwracala null ponizej pieciu zakonczonych zadan, wiec na swiezym sklepie nie pojawiala sie nigdy |
  | ruch paska                | status_to_progress('queued') = 10, ten sam procent przez cala faze kolejki |

  Stan ze zrzutu to wlasnie kolejka. Klient widzial nieruchomy pasek
  i ani jednego slowa o czasie.

CZAS LICZY SIE OD CHWILI, W KTOREJ KLIENT ZACZAL CZEKAC
* Podstawa jest created_at zadania, a nie moment, w ktorym serwer raczyl
  zaczac generowac. Klient czeka nieprzerwanie od klikniecia.
* Do MEDIANY nadal wchodzi wylacznie czas samego generowania, bo tam chodzi
  o koszt modelu, a nie o odczucie klienta. Dwie rozne wielkosci, dwa rozne
  liczniki, kazdy do swojego celu.
* Przeliczenie idzie przez kanoniczne wiek_sekund(), czyli tam, gdzie
  naprawiony jest blad stref czasowych z 3.20.0.

CZAS TYPOWY ZAWSZE JEST, NAWET BEZ POMIARU
* mediana_generowania() nadal moze zwrocic null, bo to POMIAR i pieciu
  probek naprawde nie ma po co udawac. Nowa typowy_czas_oczekiwania()
  zwraca wtedy wartosc deklarowana, bo dla KLIENTA null znaczyl pusty napis.
  Domyslnie 45 s, zmienia to filtr pp_rv_typowy_czas.
* Serwer podaje tez twardy limit czekania (filtr pp_rv_limit_oczekiwania,
  domyslnie 180 s) oraz informacje, czy czas typowy jest zmierzony.

CO KLIENT CZYTA TERAZ, ZALEZNIE OD FAZY
  | Uplyw   | Napis glowny                    | Drugi wiersz                          |
  |---------|---------------------------------|---------------------------------------|
  | brak    | Zwykle trwa okolo 45 s          | Nie zamykaj tej strony                |
  | 5 s     | Zostalo okolo 40 s              | Czekasz 5 s z typowych 45 s           |
  | 30 s    | Zostalo okolo 15 s              | Czekasz 30 s z typowych 45 s          |
  | 90 s    | Trwa dluzej niz zwykle          | Czekasz 1 min 30 s. Czekamy najdluzej do 3 min, potem przerwiemy i damy znac |
  | 200 s   | Przekroczylismy czas oczekiwania| Przerywamy i pokazemy, co poszlo nie tak |

* ZADEN z tych napisow nie moze byc pusty. Osiem roznych wejsc, w tym brak
  danych i dane niepoprawne, sprawdzanych asercjami. Pusty napis to
  dokladnie to, na co byla skarga: klient nie wie, czy czekac, czy
  odswiezyc.
* Przekroczony czas typowy NIE jest nazywany bledem ani awaria. Zmienia
  sie barwa napisu, nie jego wydzwiek, bo to nadal nie jest awaria
  i nie wolno jej udawac.

PASEK PRZESTAJE STAC
* Podloga paska rosnie z uplywem czasu, niezaleznie od tego, ze serwer
  podaje ten sam procent przez cala faze kolejki.
  Zmierzone dla czasu typowego 45 s: 0, 10, 31, 61, 90 procent
  po 0, 5, 15, 30 i 44 sekundach.
* Podloga NIE dobija do stu: konczy na 92, bo zakonczenie nalezy do
  serwera, a nie do zegara w przegladarce.
* Podloga pisze przez JEDYNY punkt zapisu, ten sam co odpowiedzi serwera,
  wiec straznik monotonicznosci obejmuje oba zrodla. Dowod wykonaniem:
  ciag 10, 61, 10, 93, 20 daje szerokosci 10, 61, 61, 93, 93. Gdyby
  podloga pisala wprost do stylu, wrocilby defekt cofajacego sie paska
  naprawiony w 3.15.0.
* Pasek ma plynne przejscie dluzsze niz odstep odpytywania oraz delikatny
  polysk. Polysk ma jedno zadanie: pokazac, ze proces zyje, gdy szerokosc
  chwilowo nie rosnie. Przy wlaczonym oszczedzaniu animacji znika, a rolę
  informowania przejmuje sam napis o czasie, ktory tyka co odpytanie.

DOWODY MUTACYJNE
  | Mutacja                                          | Asercji upadlo |
  |--------------------------------------------------|----------------|
  | powrot pustego napisu przy braku uplywu          |              8 |
  | napis mowi ile minelo zamiast ile zostalo        |              5 |
  | znika informacja, do kiedy czekamy               |              3 |
  | podloga paska przestaje rosnac                   |              1 |
  | podloga pisze z pominieciem straznika            |              1 |
  | uplyw znowu liczony tylko od generowania         |              1 |
  | brak pomiaru znowu daje null                     |              1 |
  | serwer znowu wysyla surowa mediane               |              2 |
  | znika twardy limit czekania                      |              1 |
  | pasek traci znak zycia                           |              1 |

LUKA W TESCIE ZNALEZIONA WLASNYM DOWODEM MUTACYJNYM, DWA RAZY
* Sprawdzenie animacji szukalo napisu '@keyframes pp-rv-zyje'. Zmiana nazwy
  na 'pp-rv-zyje-NIEMA' przechodzila, bo to jego podciag, a animacja bez
  pasujacych klatek po prostu nie dziala i nic tego nie zglasza.
* Poprawka pierwsza tez byla zla: brala PIERWSZA deklaracje animacji
  w calym arkuszu, a tych jest dziesiec, wiec sprawdzala cudza animacje.
  Wzorzec jest teraz zakotwiczony w regule paska i porownuje nazwe uzyta
  z nazwa zadeklarowanych klatek. Ta sama mutacja wywala 1 asercje.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.22.0 | 3.23.0 |
  |------------------------|--------|--------|
  | zestawow               |     25 |     26 |
  | asercji zaliczonych    |  1 301 |  1 367 |
  | relacji spojnosci      |     34 |     34 |
  | plikow PHP po lincie   |    148 |    148 |
  | plikow JS po lincie    |     45 |     46 |


= 3.22.0 =

KAFELEK "LICZBA PACZEK" BYL PUSTY, I TO NIE BYLA KWESTIA WYGLADU
* Zgloszenie dotyczylo projektu kolumny, ale pierwszy pomiar pokazal defekt
  danych, nie stylu.
* Przyczyna zmierzona w zrodle: kopia zgodnosciowa w build_area_payload()
  przenosila na gorny poziom odpowiedzi TYLKO piec pol: area_min, area_max,
  area_used, confidence, is_estimate. Pomijala packages, package_size,
  unit_price i waste_pct, a panel czyta je wlasnie z gory.

  | Element panelu           | Skad czyta      | Bylo   | Jest |
  |--------------------------|-----------------|--------|------|
  | Powierzchnia             | area_min/max    | dziala | dziala |
  | Szacowany koszt          | total_cost      | dziala | dziala |
  | Liczba paczek            | packages        | PUSTY  | liczba |
  | data-pkg-size            | package_size    | 0      | wartosc |
  | data-unit-price          | unit_price      | 0      | wartosc |
  | data-waste               | waste_pct       | 0      | wartosc |

* packages NIE jest kopiowane z pierwszej pozycji, tylko SUMOWANE przez
  estymator po wszystkich dostepnych pozycjach zestawu, obok kosztu, w tej
  samej petli. Kafelek mowi o zestawie, a nie o pierwszym produkcie, a dwie
  liczby powstajace w jednym miejscu nie moga sie rozjechac.
  Zmierzone na dwoch pozycjach: 11 plus 4 daje 15 paczek i 8 045,19 zl.
  Pozycja niedostepna nie wchodzi do zadnej z sum.

PASEK ZAKUPU PRZYKLEJONY DO DOLU KOLUMNY NA DESKTOPIE
* Wczesniej siedzial na samym koncu dlugiej kolumny, ponizej tabeli
  zapotrzebowania i listy produktow. Przy typowej wysokosci okna klient
  widzial go dopiero po przewinieciu calego wywodu.
* Suma i wezwanie to jedyne dwie rzeczy, ktore musza byc widoczne CALY CZAS.
  Reszta kolumny jest uzasadnieniem tej sumy i przewija sie pod nia.
* Pasek ma wlasna oslone gradientem: bez niej tresc przewijajaca sie pod
  polprzezroczystym tlem przeswituje i pasek wyglada na zepsuty. Oslona nie
  lapie klikniec.
* Na ekranie wezszym niz 900 px pasek plynie z trescia, bo przyklejanie
  w kolumnie koliduje tam z paskiem mobilnym i z dolnym menu motywu.

HIERARCHIA: JEDEN DOMINANT NA EKRANIE
* Ta sama kwota wystepowala na ekranie TRZY razy jako trzy rowne glosy:
  w kafelku "Szacowany koszt", w ciemnym pasie pod tabela i w pasku zakupu.
  Trzy rowne glosy to brak hierarchii.
* Klikalna jest tylko suma w pasku zakupu, wiec tylko ona ma prawo byc
  najglosniejsza. Ciemny pas ZOSTAJE, bo domyka tabele, ale schodzi do roli
  wiersza podsumowania: jasne tlo, mniejszy napis, etykieta "Razem z tej
  tabeli" zamiast "Wyliczone dla tego pomieszczenia", ktore brzmialo jak
  wynik glowny.
* W panelu szacunku kafelek kosztu jest mocniejszy od dwoch pozostalych,
  ale nadal cichszy od paska zakupu.

PASEK MOWI, CO KUPUJESZ, A NIE SAMO ILE TO KOSZTUJE
* Pod suma doszedl licznik skladu: "2 z 3 produktow". Odswieza sie przy
  kazdej zmianie wyboru i liczy z TEJ SAMEJ zmiennej co napis na przycisku,
  wiec obie liczby nie moga sie rozjechac.

WSZYSTKIE ELEMENTY ZOSTALY
* Trzynascie elementow kolumny sprawdzanych asercjami: suma, przycisk
  koszyka, zapis ilosci, pasek zaufania, panel szacunku, trzy kafelki,
  tabela zapotrzebowania, suma tabeli, przeliczanie metrazu, zastrzezenie
  i lista produktow. Nic nie zostalo usuniete, zmienila sie waga wizualna
  i kolejnosc czytania.

DOWODY MUTACYJNE
  | Mutacja                                     | Asercji upadlo |
  |---------------------------------------------|----------------|
  | powrot niepelnej listy pol plaskich         |              4 |
  | estymator przestaje sumowac paczki          |              2 |
  | suma paczek nie trafia do payloadu          |              1 |
  | pasek przestaje byc przyklejony             |              3 |
  | pasek traci oslone                          |              1 |
  | na mobile pasek zostaje przyklejony         |              1 |
  | licznik skladu przestaje sie odswiezac      |              2 |
  | wraca ciemny pas jako drugi bohater         |              1 |
  | znika opakowanie paska                      |              2 |
  | znika przycisk zapisu ilosci                |              1 |

DWIE LUKI W TESCIE ZNALEZIONE WLASNYM DOWODEM MUTACYJNYM
* Sprawdzenie oslony paska patrzylo na sam selektor .pp-rv-kup::before,
  ktory wystepuje DWA razy, bo na waskim ekranie oslona jest wylaczana.
  Mutacja psujaca sama regule przechodzila, bo drugie wystapienie zostawalo.
  Asercja patrzy teraz na TRESC reguly.
* Sprawdzenie obecnosci elementow porownywalo same nazwy klas.
  'pp-rv-save-qty' jest podciagiem 'pp-rv-save-qty-NIEMA', wiec zmiana nazwy
  klasy przechodzila. Trzy najwazniejsze pozycje porownuja teraz pelny
  atrybut class.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.21.0 | 3.22.0 |
  |------------------------|--------|--------|
  | zestawow               |     24 |     25 |
  | asercji zaliczonych    |  1 249 |  1 301 |
  | relacji spojnosci      |     34 |     34 |
  | plikow PHP po lincie   |    148 |    148 |
  | plikow JS po lincie    |     44 |     45 |


= 3.21.0 =

PANEL REGUL KATALOGU PRZEPISANY OD ZERA
* Zgloszenie: "nie da sie usunac kategorii, edycja, latwiejszy wybor,
  przejrzyste, wyszukiwarka, a nie lista".
* Pomiar poprzedniej wersji panelu, na zrodle:

  | Co zmierzone                              | Bylo | Jest |
  |-------------------------------------------|------|------|
  | list <select multiple>                    |    7 |    0 |
  | pol wyszukiwania                          |    0 |    6 |
  | przyciskow usuniecia pojedynczej pozycji  |    0 | przy kazdej pozycji |
  | podgladow nazwy produktu zamiast numeru   |    0 |  tak |
  | pol z surowymi numerami produktow         |    3 |    0 |

  Zeby usunac JEDNA kategorie z listy wielokrotnej, trzeba bylo trafic
  ctrl+klikiem w podswietlona pozycje. Nikt tego nie wie i nikt tego nie
  robi. Zeby dodac produkt, trzeba bylo znac jego numer z pamieci, a zly
  numer zapisywal sie po cichu.

JEDEN WYBIERAK, TA SAMA OBSLUGA WSZEDZIE
* Uklad: WYBRANE u gory jako kafelki z krzyzykiem, ponizej wyszukiwarka
  i lista trafien. Ten sam dla kategorii i dla produktow, bo to ta sama
  czynnosc: znajdz, dodaj, zobacz co masz, usun. Dwa rozne uklady na jedna
  czynnosc zmuszalyby do uczenia sie dwa razy.
* Usuniecie to JEDNO klikniecie w krzyzyk przy kafelku.
* Kafelki pokazuja NAZWY, nie numery. Numer jest z boku, mala czcionka,
  do kontroli.
* Wybrane pozycje zostaja widoczne na liscie, tylko oznaczone i przesuniete
  na koniec. Ukrycie ich sprawia, ze wlasciciel szuka czegos, co juz ma.

WYSZUKIWARKA, KTORA NIE KLAMIE
* Kategorie: szukanie po nazwie, po pelnej sciezce w drzewie i po numerze.
  "sztukateria" znajduje "Listwy scienne", bo to jej dziecko.
* Produkty: osobny punkt AJAX w panelu, szukanie po nazwie, SKU i numerze.
  Numer wpisany wprost trafia natychmiast. Fraza krotsza niz dwa znaki nie
  obciaza bazy.
* POLSKIE ZNAKI dzialaja w obie strony: "scienne" znajduje "Ścienne"
  i odwrotnie. Bez tego wyszukiwarka dawalaby falszywe zero wynikow, czyli
  bylaby gorsza od przewijania, bo przewijanie przynajmniej nie klamie.
* Kazde slowo frazy musi wystapic. Bez tego "lamele scienne" trafialoby we
  wszystko, co zawiera samo "scienne".

USUNIECIE OSTATNIEJ POZYCJI TERAZ SIE ZAPISUJE
* Kazdy wybierak wysyla POLE-ZNACZNIK o pustej wartosci, takze przy pustym
  wyborze. Bez niego usuniecie ostatniej pozycji nie wyslaloby tego klucza
  w ogole i stara wartosc przetrwalaby zapis. To jest dokladnie skarga
  "nie da sie usunac", w jej najgorszej postaci: klient usuwa, zapisuje
  i widzi to samo.

DROBIAZGI, KTORE DECYDUJA O TYM, CZY PANEL JEST UZYWALNY
* Enter w wyszukiwarce NIE wysyla calego formularza ustawien.
* Krzyzyk usuwania ma widoczne ognisko dla klawiatury i wlasne pole
  klikniecia, nie jest znakiem w tekscie.
* Drzewo kategorii liczone RAZ na strone i podane wszystkim szesciu
  wybierakom w jednym bloku danych. Renderowanie pelnej listy w kazdym polu
  osobno rozdymalo strone i bylo powodem, dla ktorego wybor byl lista.
* Kazda plaszczyzna dostala wlasna karte z krawedzia, zamiast plaskiego
  ciagu naglowkow.
* Zasoby panelu ladowane WYLACZNIE na stronie wizualizatora, po parametrze
  strony, a nie po uchwycie: uchwyt zmienia sie przy przestawieniu pozycji
  w menu i juz raz wylaczyl zasoby po cichu.

BEZPIECZENSTWO WYSZUKIWARKI PANELU
* Sprawdzane uprawnienie manage_woocommerce i token. Endpoint jest tylko
  dla zalogowanych (wp_ajax bez nopriv).

DOWODY MUTACYJNE
  | Mutacja                                      | Asercji upadlo |
  |----------------------------------------------|----------------|
  | usuwanie przestaje dzialac                   |              3 |
  | polskie znaki przestaja byc normalizowane    |              1 |
  | kazde slowo frazy przestaje byc wymagane     |              5 |
  | wybrane znikaja z konca listy                |              1 |
  | pole-znacznik znika ze skryptu               |              1 |
  | wraca lista wielokrotna                      |              1 |
  | wyszukiwarka bez sprawdzania uprawnien       |              2 |
  | serwer przestaje podawac nazwy wybranych     |              1 |
  | krzyzyk traci ognisko dla klawiatury         |              1 |
  | zasoby ladowane na kazdej stronie panelu     |              1 |

LUKA W TESCIE ZNALEZIONA WLASNYM DOWODEM MUTACYJNYM
* Sprawdzenie nazw wybranych patrzylo tylko na obecnosc atrybutu
  data-nazwy. Mutacja podmieniajaca jego WARTOSC na pusty lancuch
  przechodzila, a kafelki pokazywalyby wtedy numery, czyli dokladnie to,
  na co byla skarga. Asercja rozbita na trzy: funkcja istnieje, atrybut
  jest wypelniony jej wynikiem, wybierak dostaje go razem z numerami.

LITEROWKA UKRYTA PRZEZ ZAPAS
* Skrypt czytal dane przez wyrazenie z dwoma nazwami obiektu, z ktorych
  pierwsza byla literowka. Dzialalo przez zapas, a literowka byla
  niewidoczna. Zostala JEDNA nazwa; asercja pilnuje, ze po obu stronach
  jest ta sama i wystepuje dokladnie raz.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.20.1 | 3.21.0 |
  |------------------------|--------|--------|
  | zestawow               |     23 |     24 |
  | asercji zaliczonych    |  1 191 |  1 249 |
  | relacji spojnosci      |     34 |     34 |
  | plikow PHP po lincie   |    148 |    148 |
  | plikow JS po lincie    |     42 |     44 |


= 3.20.1 =

DWIE PODLOGI PRZECHODZILY, BO BLOKADA OPIERALA SIE NA ZGADYWANIU
* Zgloszenie: "logika pozwala na wybranie 2 podlog".
* Przyczyna zmierzona: istniejaca blokada slotow montazowych dziala na
  wyniku PP_RV_Product_Classifier, ktory rozpoznaje typ produktu
  HEURYSTYCZNIE, z nazwy, atrybutow, opisu i tekstu kategorii. Gdy nie
  trafi, typ wychodzi "other", slot jest pusty, a produkty z pustym slotem
  z definicji nie biora udzialu w blokadzie. Dwie podlogi przechodza.
* Nowy mechanizm liczy limit z KATEGORII, czyli z twardego faktu, a nie
  z rozpoznania po nazwie. Stara blokada slotow zostaje: dziala tam, gdzie
  klasyfikator trafia, i wtedy zamienia dokladnie tak jak wczesniej.

LIMIT ILOSCIOWY NA KATEGORIE, DO USTAWIENIA W PANELU
  | Ustawienie                        | Gdzie                          |
  |-----------------------------------|--------------------------------|
  | Maks. produktow z danej kategorii | przy kazdej plaszczyznie osobno |
  | Domyslny limit na kategorie       | sekcja wspolna                  |
* Pola limitow generuja sie z kategorii JUZ ZAPISANYCH dla plaszczyzny.
  Wolne pole tekstowe z parami ID i liczba byloby drugim sposobem na to
  samo, a dwa sposoby zawsze sie rozjezdzaja.
* Ustawienie domyslnego limitu na 1 zalatwia zgloszenie od reki, bez
  wpisywania czegokolwiek przy poszczegolnych kategoriach.
* Limit obejmuje PODKATEGORIE: limit na "Podlogi" dziala na produkt lezacy
  w "Panele winylowe", bo wlasciciel mysli o podlogach, a nie o lisciach
  drzewa kategorii.
* Puste = bez limitu. Zero tez znaczy brak limitu, a nie zakaz: od zakazu
  jest wykluczenie kategorii i ma osobne pole.

ZAMIANA, NIE ODMOWA
* Po przekroczeniu limitu NAJSTARSZY produkt z tej kategorii zostaje
  zamieniony na nowy, z komunikatem. Klient, ktory ma podloge i klika
  druga, chce ZMIENIC podloge, a nie dowiedziec sie, ze nie wolno.
  Odmowa zmusza go do szukania, ktory kafelek odznaczyc.

KONTROLA PO OBU STRONACH
* Przegladarka zamienia od razu, zeby klient widzial skutek.
* Serwer sprawdza to samo NIEZALEZNIE, w obu wejsciach (wgranie zdjecia
  i generowanie). Kontrola wylacznie po stronie klienta jest sugestia,
  nie regula.
* Kategoria limitu i jego wartosc sa liczone PO STRONIE SERWERA i wysylane
  w danych produktu jako limit_kat oraz limit_kat_max. Przegladarka tylko
  zlicza. Wyprowadzanie tego w kliencie oznaczaloby druga definicje regul
  i rozjazd przy pierwszej zmianie ustawien.

DOWODY MUTACYJNE
  | Mutacja                                        | Asercji upadlo |
  |------------------------------------------------|----------------|
  | limit przestaje blokowac druga podloge         |              5 |
  | limit domyslny przestaje obowiazywac           |              1 |
  | limit nie siega kategorii nadrzednej           |              1 |
  | bramka serwera przestaje liczyc                |              3 |
  | zamiana bierze najnowszy zamiast najstarszego  |              1 |
  | serwer przestaje wysylac wartosc limitu        |              1 |
  | bramka serwera znika z generowania             |              1 |
  | klient przestaje sprawdzac limit               |              2 |
  | klient wyprowadza limit sam z drzewa           |              2 |
  | panel traci pola limitow                       |              1 |

DWIE LUKI W TESCIE ZNALEZIONE WLASNYM DOWODEM MUTACYJNYM
* Sprawdzenie kategorii limitu uzywalo przypadku, w ktorym obie galezie
  funkcji daja te sama odpowiedz, wiec wygaszenie jednej z nich nie
  wywolywalo bledu. Dodany przypadek, w ktorym pierwsza kategoria produktu
  NIE ma limitu, a druga ma. Ta sama mutacja wywala teraz 1 asercje.
* Sprawdzenie kolejnosci w konfiguratorze bylo zapisane jako
  strpos( a ) < strpos( b ). Przy braku a strpos zwraca false, a false
  mniejsze od liczby jest w PHP prawda, wiec asercja przechodzila po
  USUNIECIU wywolania. Rozbite na trzy: istnienie obu i dopiero potem
  kolejnosc. Ta sama mutacja wywala teraz 2 asercje.

RELACJA R32 ZAWEZONA PO FALSZYWYM ALARMIE
* Sprawdzenie "kazdy nieterminalny status zadania ma droge wyjscia"
  liczylo KAZDA tablice z kluczem 'status' i zglosilo blad na wyniku
  decyzji o limicie ( 'status' => 'zamien' ), ktory z zadaniem nie ma nic
  wspolnego. Wzorzec czyta teraz wylacznie zapisy w oknie po create_job()
  i update_job(). Dowod mutacyjny potwierdza, ze prawdziwa slepa uliczka
  nadal jest wykrywana.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.20.0 | 3.20.1 |
  |------------------------|--------|--------|
  | zestawow               |     23 |     23 |
  | asercji zaliczonych    |  1 143 |  1 191 |
  | relacji spojnosci      |     34 |     34 |
  | plikow PHP po lincie   |    148 |    148 |
  | plikow JS po lincie    |     42 |     42 |


= 3.20.0 =

GENEROWANIE STALO, BO ZEGAR LICZYL NA DWOCH SKALACH NARAZ
* Zgloszenie: "generowanie stoi, pasek nie rusza, na zdjeciach ktore
  wczesniej dzialaly".
* Przyczyna zmierzona: kolumny czasu zapisuje current_time( 'mysql' ),
  czyli czas LOKALNY strony, a WordPress ustawia strefe PHP na UTC.
  strtotime() czyta wiec ten napis jako UTC.

  | Co                                  | Wartosc                |
  |-------------------------------------|------------------------|
  | teraz UTC                           | 2026-08-02 20:37:51    |
  | updated_at w bazie (lokalny, UTC+2) | 2026-08-02 22:37:51    |
  | time() minus strtotime( updated_at )| -7200 s                |

* Skutek: KAZDY prog czasowy w wizualizatorze byl martwy.

  | Prog                              | Przed  | Po naprawie |
  |-----------------------------------|--------|-------------|
  | karencja kolejki                  | NIGDY  | 20 s        |
  | wykrywanie zawieszonego generowania| NIGDY | 120 s       |
  | wykrywanie zawieszonej analizy     | NIGDY | 90 s        |

  Zeby warunek 90 s w ogole zaskoczyl, trzeba bylo czekac 2 godziny
  i 90 sekund. Klient odpada po pieciu minutach.
* Znaczy to, ze generowanie dzialalo WYLACZNIE wtedy, gdy zadanie podjal
  Action Scheduler. Gdy kolejka milczala, sciezka ratunkowa nie mogla
  wejsc i zadanie stalo w statusie queued do konca limitu.
* Blad jest w oryginale 3.14.0, nie w ostatnich wydaniach. NIE ZMIENIA TO
  FAKTU, ze naprawa zawieszonej analizy z 3.18.1 stala na tym samym zlym
  porownaniu i nigdy nie mogla zadzialac, a zostala opisana jako dzialajaca.

JEDNA METODA DO LICZENIA WIEKU
* PP_Room_Visualizer_DB::wiek_sekund() przelicza date lokalna na UTC przez
  get_gmt_from_date(), czyli te sama skale, ktorej uzywa time(). To jedyne
  poprawne porownanie i jedyne miejsce, w ktorym wolno je robic.
* Poza wizualizatorem (pakiet growth) uzyty jest wprost prymityw
  WordPressa, bo growth startuje niezaleznie i siegniecie po klase
  wizualizatora byloby bledem krytycznym przy innej konfiguracji wtyczki.
* Poprawione takze: odstep miedzy wyzwalaczami, wiek porzuconego koszyka
  i liczba dni od zdarzenia. Wszystkie byly zanizone o przesuniecie strefy.

KARENCJA KOLEJKI SKROCONA Z 90 DO 20 SEKUND
* Dziewiecdziesiat sekund dobrano przy warunku, ktory nigdy sie nie
  spelnial, wiec ta liczba nigdy nie byla sprawdzona w praktyce. Po
  naprawie zegara realnie trzyma klienta przed nieruchomym paskiem.
  Zmienia to filtr pp_rv_queue_grace_seconds.

KATALOG DLA PLASZCZYZN: NOWA SEKCJA W USTAWIENIACH
* Zgloszenie: mozliwosc dopasowania, ktore kategorie pokazuja sie dla
  ktorej plaszczyzny, plus wykluczanie kategorii, podkategorii
  i konkretnych produktow albo wybor tylko wskazanych.
* Do 3.19.1 wizualizator mial JEDNA plaska liste kategorii wspolna dla
  podlogi, sciany i sufitu. Klient z zaznaczona sama podloga i tak
  przegladal lamele i rozety.

  Co da sie ustawic dla KAZDEJ plaszczyzny osobno
  | Pole                  | Dziala tak                                   |
  |-----------------------|----------------------------------------------|
  | Widoczne kategorie    | tylko one, razem z podkategoriami            |
  | Wykluczone kategorie  | znikaja razem z podkategoriami               |
  | Wykluczone produkty   | znikaja niezaleznie od kategorii             |
  | Tylko te produkty     | lista zamknieta, nic spoza niej              |
  Plus sekcja wspolna: wykluczenia obowiazujace przy kazdej plaszczyznie.

* KOLEJNOSC ROZSTRZYGANIA, swiadoma i jedyna:
  1. lista zamknieta zawezajaca zbior,
  2. wykluczenia usuwajace z tego, co zostalo,
  3. kategorie plaszczyzny na reszcie.
  WYKLUCZENIE BIJE WYBOR, takze liste zamknieta. Odwrotna kolejnosc
  znaczylaby, ze dopisanie produktu do listy po cichu odblokowuje cos,
  co wlasciciel swiadomie zablokowal.

* Kategorie sa wybierane z drzewa kategorii sklepu, z wcieciami
  odpowiadajacymi hierarchii. Produkty podaje sie identyfikatorami po
  przecinku.
* ZGODNOSC WSTECZ: pusta konfiguracja znaczy zachowanie sprzed 3.20.0,
  czyli wspolna lista z pola "Dozwolone kategorie produktow". Nowa opcja
  nie zmienia dzialania sklepu, dopoki nie zostanie wypelniona. Pilnuja
  tego cztery asercje.
* Konfigurator wysyla teraz zaznaczony zakres razem z kazdym wyszukaniem,
  bo bez tego serwer nie mialby czym zawezic listy.

NOWA RELACJA SPOJNOSCI (33 -> 34)
* Zaden plik nie moze porownywac time() z data z bazy bez przeliczenia
  strefy. To jest ogolna postac usterki wyzej i objawia sie cisza: prog
  nie dziala, a nic nie zglasza bledu. Relacja ma licznik niewaznosci.

DOWODY MUTACYJNE
  | Mutacja                                         | Asercji upadlo |
  |-------------------------------------------------|----------------|
  | powrot zlego zegara                             |              2 |
  | karencja wraca do 90 s                          |              1 |
  | wykluczenie przestaje bic liste zamknieta       |              3 |
  | kategorie plaszczyzny przestaja zawezac         |              2 |
  | lista zamknieta przestaje dzialac               |              1 |
  | pusty przekroj listy pokazuje caly katalog      |              1 |
  | wykluczenia globalne przestaja obowiazywac      |              3 |
  | lista kategorii ignoruje zakres                 |              1 |
  | front przestaje wysylac zakres                  |              1 |
  | sanitizer zapisuje tablice zamiast JSON         |              1 |
  | R34: powrot surowego porownania                 |              1 |
  | R34: wzorzec przestaje pasowac                  |              1 |

LUKA W TESCIE ZNALEZIONA WLASNYM DOWODEM MUTACYJNYM
* Pierwsza wersja testu zegara chodzila WYLACZNIE po galezi zapasowej
  wiek_sekund(), bo atrapa get_gmt_from_date() nie istniala. Cofniecie
  naprawy w galezi produkcyjnej nie wywolywalo ani jednego bledu; pokazal
  to dopiero dowod mutacyjny, nie asercja. Po dolozeniu atrapy ta sama
  mutacja wywala 2 asercje. Dodana jest tez asercja pilnujaca, ze atrapa
  jest na miejscu.

STRAZNIK OZNACZONY UCZCIWIE, A NIE UDAWANY
* Dowod mutacyjny pokazal, ze usuniecie warunku skonfigurowane()
  w zastosuj() NIE zmienia wyniku: przy pustej konfiguracji nie ma czego
  dopisac do zapytania. Warunek zostaje jako skrot wydajnosciowy i jest
  tak opisany w kodzie, zamiast udawac regule poprawnosci.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.19.1 | 3.20.0 |
  |------------------------|--------|--------|
  | zestawow               |     22 |     23 |
  | asercji zaliczonych    |  1 062 |  1 143 |
  | relacji spojnosci      |     33 |     34 |
  | plikow PHP po lincie   |    146 |    148 |
  | plikow JS po lincie    |     42 |     42 |


= 3.19.1 =

BLOKADA KROKOW Z 3.19.0 BYLA BEZ SKUTKU NA STARCIE
* Reklamacja wlasciciela: kroki maja byc na poczatku wyszarzone i
  nieklikalne, a zapalac sie po kolei. Po 3.19.0 nic sie nie wyszarzalo.
* Przyczyna zmierzona w rdzeniu: selectedSurfaces startowalo z wartoscia
  ['floor']. Krok pierwszy byl wiec domkniety, ZANIM klient cokolwiek
  kliknal, a to odblokowywalo wszystkie kolejne.

  | Stan po 3.19.0, uruchomiony        | Wynik   |
  |------------------------------------|---------|
  | krokow wyszarzonych na starcie     | 0 z 2   |
  | krok 1 gotowy przed klikiem        | tak     |
  | krok 2 dostepny przed klikiem      | tak     |

* Zakres startuje teraz PUSTY. Efekt, uruchomiony po zmianie:

  | Stan po 3.19.1                     | Wynik   |
  |------------------------------------|---------|
  | krokow wyszarzonych na starcie     | 1 z 2   |
  | dostepny jest wylacznie krok 1     | tak     |
  | po kliknieciu powierzchni krok 2   | zapala sie |

* Klient wchodzacy z karty produktu albo z inspiracji ma produkty wybrane
  z gory, wiec zakres jest z nich WYPROWADZANY i nadal startuje
  z kompletem. Powierzchnia czytana jest przez te sama funkcje, ktorej
  uzywa regula krokow, wiec nie ma drugiej definicji.

USUNIETE WSZYSTKO, CO 3.19.0 DOLOZYLO DO OKNA
* Wlasciciel odrzucil dodane elementy jednoznacznie: okno ma zostac takie,
  jakie bylo, a kroki maja po prostu zapalac sie po kolei.

  | Element z 3.19.0                      | Status   |
  |---------------------------------------|----------|
  | pasek krokow nad konfiguratorem       | usuniety |
  | lista brakow pod przyciskiem          | usunieta |
  | wiersz podsumowania w kazdej sekcji   | usuniety |
  | wiersz braku w kazdej sekcji          | usuniety |
  | nawigacja po krokach                  | usunieta |
  | 165 wierszy CSS krokomierza i brakow  | usuniete |

* Zostaje WYLACZNIE zmiana stanu sekcji, ktore i tak juz byly w oknie:
  wygaszenie, blokada sterowan i znak potwierdzenia na numerze kroku.
* Dziesiec asercji pilnuje, zeby te elementy nie wrocily przy nastepnej
  zmianie. Sprawdzany jest i konfigurator, i arkusz stylow.

INSTRUKCJA WESZLA W NAPIS PRZYCISKU, KTORY JUZ BYL
* Wygaszony przycisk bez powodu zmusza klienta do zgadywania. Zamiast
  osobnego komunikatu napis samego przycisku mowi, co zrobic:

  | Stan konfiguratora        | Napis przycisku            |
  |---------------------------|----------------------------|
  | nic nie wybrane           | Zaznacz, co ma się zmienić |
  | zakres wybrany            | Wybierz produkty           |
  | kolor zaznaczony, brak go | Wybierz kolor ściany       |
  | komplet                   | Dalej: Wgraj zdjęcie       |

  Zero nowych elementow, zero pytania "czego brakuje".

KOLEJNOSC WCZYTYWANIA
* Rdzen wyprowadza zakres z produktow, wiec regula krokow musi byc
  wczytana przed nim. Zaleznosc jest zadeklarowana jawnie, a nie
  zostawiona kolejnosci wypisywania skryptow. Rejestracja jest teraz
  w JEDNYM miejscu; wczesniej stala przy konfiguratorze.

DOWODY MUTACYJNE
  | Mutacja                                     | Asercji upadlo |
  |---------------------------------------------|----------------|
  | powrot podlogi zaznaczonej z gory           |              2 |
  | zakres przestaje byc wyprowadzany z produktow|             1 |
  | powrot paska krokow do okna                 |              1 |
  | napis przycisku przestaje niesc instrukcje  |              1 |
  | wygaszenie sekcji znika z arkusza           |              1 |
  | rdzen traci zaleznosc od reguly krokow      |              1 |
  | kroki nigdy sie nie blokuja                 |              4 |

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.19.0 | 3.19.1 |
  |------------------------|--------|--------|
  | zestawow               |     22 |     22 |
  | asercji zaliczonych    |  1 043 |  1 062 |
  | relacji spojnosci      |     33 |     33 |
  | plikow PHP po lincie   |    146 |    146 |
  | plikow JS po lincie    |     42 |     42 |


= 3.19.0 =

KROKI Z BLOKADA: WYBIERASZ 1, ODBLOKOWUJE SIE 2
* Zgloszenie: "podswietlane kroki, aby wyeliminowac bledy i braki".
* Blokada jest tylko widoczna czescia. Prawdziwa robota to dwie kontrole,
  ktorych wczesniej NIE BYLO NIGDZIE.

  Co zmierzone przed zmiana
  | Co sprawdzane                                  | Trafien |
  |------------------------------------------------|---------|
  | zgodnosc produktu z wybranym zakresem (klient)  |       0 |
  | zgodnosc produktu z wybranym zakresem (serwer)  |       0 |
  | powierzchnia w zakresie bez czegokolwiek        |       0 |
  | regula odblokowania                             | jeden bool z trzech warunkow |
  | pol w sciezce pojedynczego produktu             | 4 z 8   |

  Skutki, ktore z tego wynikaly:
  - lamela scienna szla do modelu przy zakresie obejmujacym sama podloge.
    Klient nie widzial jej na wyniku i nie mial jak sie dowiedziec dlaczego.
  - zaznaczony sufit bez produktu sufitowego dawal ten sam sufit, co byl.
  - przycisk "Dalej" gasl, ale NIE MOWIL, czego brakuje.

CZYSTA FUNKCJA JAKO JEDYNE ZRODLO PRAWDY
* Nowy plik assets/js/room-viz/room-viz-kroki.js. Dostaje stan, zwraca
  opis krokow. Nie dotyka DOM, wiec chodzi w tescie z linii polecen.
* Wyglad jest jej ODBICIEM: jedna metoda malujKroki() nadaje klasy,
  numeruje i wypisuje braki. Druga regula w CSS albo w renderze
  rozjechalaby sie przy pierwszej zmianie.

  | Krok         | Domkniety, gdy                                        |
  |--------------|-------------------------------------------------------|
  | 1 Zakres     | zaznaczona co najmniej jedna powierzchnia             |
  | 2 Kolor      | kolor wybrany ALBO klient zrezygnowal ze zmiany       |
  | 3 Produkty   | jest produkt, kazda powierzchnia z zakresu ma pokrycie, zaden produkt nie wychodzi poza zakres, limit niezlamany |
* Krok koloru istnieje TYLKO przy zaznaczonej scianie. Numeracja idzie
  wtedy 1-2-3, a bez sciany 1-2, bez dziury po nieistniejacym kroku.
* Sciana jest domknieta takze sama zmiana koloru. Wymaganie produktu
  sciennego bylby bledem w druga strone: klient chcacy tylko przemalowac
  sciane nie ma czego kupowac.

BLAD ELIMINOWANY, A NIE ZGLASZANY
* Wybor produktu sciennego przy zakresie bez sciany nie jest bledem
  klienta, tylko brakiem w zakresie. Powierzchnia jest DOPISYWANA
  automatycznie, z komunikatem: "Dodano sciane do zakresu, bo tego wymaga
  wybrany produkt". Kontrola w regule krokow zostaje jako siatka
  bezpieczenstwa na stan przywrocony z sesji.

BLOKADA MUSI BYC REALNA, NIE TYLKO WIDOCZNA
* Sekcja zablokowana dostaje atrybut disabled na wszystkich przyciskach,
  polach i listach. Sama klasa CSS z opacity zostawia je klikalne dla
  klawiatury i czytnika ekranu, wiec blokada bylaby pozorna.
* Odblokowanie zdejmuje WYLACZNIE to, co ta blokada zalozyla (znacznik
  data-krok-zablokowany). Bez tego odblokowanie krokow wlaczaloby takze
  kafelki produktow wygaszone limitem.
* Sekcja zablokowana zostaje WIDOCZNA, tylko wygaszona. Ukrycie byloby
  gorsze: klient nie wie, ile jeszcze przed nim.

KANONICZNA MAPA SLOT NA POWIERZCHNIE
* Slot mowi, CO to za element, a nie KTORA powierzchnia musi byc
  w zakresie. Nowa metoda PP_RV_Product_Classifier::surface_for_slot().
  Wszystkie 8 slotow ma odwzorowanie, pilnuje tego asercja.

  | Slot                                    | Wymaga    |
  |-----------------------------------------|-----------|
  | floor, skirting                         | podlogi   |
  | wall, wall_moulding, wall_slat, curtain_cover | sciany |
  | ceiling, ceiling_moulding, rosette      | sufitu    |

* Klient NIE wyprowadza tego sam. Serwer wysyla gotowe pole surface,
  policzone raz, z jednego miejsca.

DRUGA SCIEZKA DANYCH PRODUKTU BYLA UBOZSZA
* Zmierzone: handle_search_products dla pojedynczego produktu zwracalo
  4 pola (id, name, price, thumb), a lista glowna 8 (ze slotem
  i etykieta). Produkt dodany ta droga nie bral udzialu ani w kontroli
  slotow, ani w kontroli zakresu, wiec blokada przepuszczalaby go bez
  slowa. Obie sciezki maja teraz ten sam ksztalt.

NOWA RELACJA SPOJNOSCI (32 -> 33)
* Slot i powierzchnia produktu podrozuja razem. Kto wysyla jedno, musi
  wyslac drugie. To jest ogolna postac usterki wyzej. Relacja ma licznik
  niewaznosci.

DOWODY MUTACYJNE
  | Mutacja                                        | Asercji upadlo |
  |------------------------------------------------|----------------|
  | kroki nigdy sie nie blokuja                    |              2 |
  | produkt spoza zakresu przestaje byc bledem     |              3 |
  | powierzchnia bez pokrycia przestaje byc brakiem|              4 |
  | sciany nie da sie domknac samym kolorem        |              1 |
  | rezygnacja z koloru przestaje domykac krok     |              3 |
  | automatyczne uzupelnienie zakresu wylaczone    |              1 |
  | blokada tylko wygladem, bez disabled           |              1 |
  | slot lameli traci powierzchnie                 |              1 |
  | sciezka pojedynczego produktu bez powierzchni  |              1 |
  | limit produktow przestaje byc sprawdzany       |              2 |
  | R33: pojedynczy produkt traci powierzchnie     |              1 |
  | R33: wzorzec przestaje pasowac                 |              1 |

ASERCJA ZMIENIONA, NIE NAPRAWIONY KOD
* Sprawdzenie "istnieje jeden malarz krokow" liczylo wystapienia napisu
  malujKroki(b, stan) i trafialo takze na jego wywolanie, dajac 2 zamiast
  1. Rozbite na dwie asercje z kotwica: jedna definicja i jedno wywolanie.
* Relacja R33 w pierwszej wersji liczyla kazdy klucz 'surface' i trafiala
  na raport konfliktu powierzchni, ktory z danymi produktu nie ma nic
  wspolnego. Dawalo to 3 wobec 2 i falszywy alarm. Wzorzec zakotwiczony
  na kanonicznej metodzie pomocniczej.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.18.1 | 3.19.0 |
  |------------------------|--------|--------|
  | zestawow               |     21 |     22 |
  | asercji zaliczonych    |    960 |  1 043 |
  | relacji spojnosci      |     32 |     33 |
  | plikow PHP po lincie   |    146 |    146 |
  | plikow JS po lincie    |     40 |     42 |


= 3.18.1 =

BRAMKA ROZMIARU ODRZUCALA ZDJECIA PIONOWE
* Zgloszenie: "nie moge wrzucic zadnego pionowego zdjecia".
* Przyczyna zmierzona w pipeline: warunek brzmial
  $size[0] < 800 || $size[1] < 600, czyli porownywal SZEROKOSC z 800
  i WYSOKOSC z 600. Milczaco zakladal kadr poziomy.

  Asymetria obrotu, zmierzona
  | Kadr      | Werdykt    | Ten sam kadr obrocony | Werdykt   |
  |-----------|------------|-----------------------|-----------|
  | 800x600   | przechodzi | 600x800               | ODRZUCONE |
  | 1024x768  | przechodzi | 768x1024              | ODRZUCONE |
  | 1280x720  | przechodzi | 720x1280              | ODRZUCONE |
  To samo zdjecie obrocone o 90 stopni raz przechodzilo, raz nie.
  Kadr 720x1280 ma 921 600 pikseli, prawie dwa razy wiecej niz prog
  480 000, i lecial. Na osmiu typowych kadrach pionowych z telefonow
  odrzucanych bylo 5.

* Nowa bramka patrzy na KROTSZY bok (min. 600), DLUZSZY bok (min. 800)
  i liczbe pikseli (min. 480 000, dokladnie tyle co 800x600). Obrot nie
  zmienia werdyktu, co pilnuje siedem par asercji.

ZDJECIE ZA MALE JEST POWIEKSZANE, A NIE ODRZUCANE
* Wczesniej odrzucenie za rozmiar wracalo surowym WP_Error z
  validate_and_save, czyli PRZED powstaniem zadania. Caly mechanizm
  ratunku (PP_RV_Photo_Rescue) nigdy tego przypadku nie widzial.
* Teraz brakujace piksele sa dokladane skalowaniem, do dwukrotnosci.
* Sufit dwukrotnosci NIE jest ostroznoscia, tylko wynikiem pomiaru:

  | Wariant                    | k01 zakres | k06 ziarno |
  |----------------------------|------------|------------|
  | zrodlo 448x600             |      12,19 |     3,5735 |
  | po powiekszeniu 2x Lanczos |      12,18 |     1,4265 |
  Zakres dynamiki sie nie zmienil, a ziarno spadlo o 60 procent.
  Powiekszenie NIE odzyskuje szczegolu, tylko wygladza. Ma wiec jedno
  zadanie: przekroczyc prog. Powyzej dwukrotnosci robi sie papka
  i uczciwiej jest poprosic o inne zdjecie.

AUTOMATYCZNA KOREKTA EKSPOZYCJI PRZED ANALIZA
* Analiza odrzuca zdjecia z kodem za_ciemne, wiec korekta wchodzi PRZED
  nia. Jest deterministyczna: tablica 256 wartosci, ta sama dla R, G i B.
  Nie potrafi przesunac mebla, bo nie zna pojecia mebla.

  Kontrolowany eksperyment: trzy zdjecia testowe, pomiar WLASNYM kodem
  wtyczki (PP_Insp_Metrics, ten sam, ktory ocenia stopien fotograficzny)
  | Wariant                    | k01 dr | k02 czern% | k03 biel% | k04 p25 | k08 nas. |
  |----------------------------|--------|------------|-----------|---------|----------|
  | baza ciemna                |  12,20 |      16,65 |      0,55 |   0,105 |    0,767 |
  | autolevel 0,5/99,5         |  13,90 |      18,37 |      2,66 |   0,090 |    1,000 |
  | jasnosc x1,18              |  12,49 |      16,40 |      2,74 |   0,122 |    0,768 |
  | gamma 1,45 na sztywno      |  11,79 |       5,82 |      2,43 |   0,193 |    0,742 |
  | GAMMA ADAPTACYJNA (wdrozona)| 12,40 |       6,50 |      0,57 |   0,137 |    0,767 |
  | kontrola neg. gamma 2,2    |  11,37 |       4,55 |      2,56 |   0,317 |    0,717 |
  | kontrola neg. przesada     |  15,60 |      29,64 |      9,89 |   0,041 |    1,000 |

  ODRZUCONE KANDYDATURY I POWOD Z TABELI:
  - autolevel na percentylach: POGARSZA zdjecie niedoswietlone. Podnosi
    punkt czerni, wiec dociska cienie (k02 16,65 -> 18,37) i obniza
    percentyl 25 (0,105 -> 0,090). Zakladalem, ze wygra. Przegral,
    i wdrozenie go pogorszyloby dokladnie to, co bylo zgloszone.
  - stala gamma 1,45: skuteczna na cieniach, ale wypala swiatla
    OSMIOKROTNIE mocniej niz wersja adaptacyjna (2,43 wobec 0,57)
    i zbija zakres dynamiki oraz nasycenie.
  - podbicie jasnosci: rusza wszystko naraz, wiec swiatla placa za cienie.

  Wersja adaptacyjna wygrywa na KAZDEJ osi naraz: cienie odzyskane
  (16,65 -> 6,50), swiatla praktycznie nietkniete (0,55 -> 0,57), zakres
  dynamiki WYZSZY niz w bazie (12,20 -> 12,40), nasycenie bez zmian.

* Gamma jest liczona z pomiaru percentyla 25, nie ustawiona z gory.
  Zdjecie w pasmie 0,14 do 0,24 dostaje gamme 1,0, czyli zostaje
  nietkniete. Sufit 1,6: skrajnie ciemne zdjecie nadal jest odrzucane,
  zamiast byc rozjasnione do szarej mgly.
* Wylacznik: opcja pp_rv_auto_korekta ustawiona na 0.

ZADANIE ZAWIESZALO SIE NA STATUSIE ANALYZING
* Zgloszenie: "zacina sie na ekranie sprawdzania", nawet ze zdjeciem,
  ktore wczesniej dzialalo. Na zrzucie komunikat "Generowanie trwa zbyt
  dlugo" po pieciu minutach.
* Przyczyna zmierzona: analiza biegnie SYNCHRONICZNIE w zadaniu
  pp_rv_upload, a rekord zadania powstaje ZANIM ona ruszy. Smierc tego
  zadania zostawiala rekord w statusie analyzing na zawsze.

  | Status     | Mial sciezke ratunku przed 3.18.1 |
  |------------|-----------------------------------|
  | queued     | tak                               |
  | pending    | tak                               |
  | analyzed   | tak                               |
  | generating | tak, po 120 s                     |
  | analyzing  | NIE                               |

* Zawieszona analiza jest teraz konczona statusem failed po 90 s
  (filtr pp_rv_analyzing_timeout), z komunikatem mowiacym wprost, ze
  przerwala sie analiza, a nie generowanie.

CO NAJPEWNIEJ ZABIJALO TO ZADANIE PO WLACZENIU IMAGICKA
* Jedno zadanie wgrania wykonywalo CZTERY pelne dekompresje obrazu:
  zmniejszenie do 1920, ponowny zapis w strip_exif, metadane zalacznika
  i przygotowanie do modelu. Imagick trzyma bufor 8 bajtow na kanal,
  wiec zdjecie 12 Mpx kosztuje wielokrotnie wiecej pamieci niz na GD.
* Drugi zapis w strip_exif byl ZBEDNY: zapis przez WP_Image_Editor
  tworzy plik od nowa, wiec bloki EXIF i tak znikaja przy zmniejszaniu.
  strip_exif chodzi teraz tylko wtedy, gdy tego zapisu nie bylo. Jedna
  dekompresja mniej na kazdym zdjeciu z telefonu.
* Zadanie wgrania podnosi limit pamieci przez wp_raise_memory_limit.
  To nie naprawia przyczyny, tylko usuwa najczestszy powod smierci;
  przyczyne zamyka wykrywanie zawieszonej analizy.

SCIEZKA RATUNKOWA BYLA WYLACZANA PRZEZ NIEZWIAZANA OPCJE
* Warunek brzmial
  is_user_logged_in() && get_option( 'pp_rv_require_login_generate' ) !== '0'
  i mial skutek odwrotny do opisu w komentarzu obok: wlaczenie
  generowania dla gosci wylaczalo synchroniczny fallback TAKZE
  zalogowanym. Zadanie stojace w kolejce nie mialo wtedy kto ruszyc,
  jezeli Action Scheduler nie pracowal.
* Zostaje samo is_user_logged_in(), czyli dokladnie to, co komentarz
  opisywal od poczatku.

KOMUNIKAT O PRZEKROCZENIU CZASU MOWIL NIEPRAWDE
* "Generowanie trwa zbyt dlugo" pojawialo sie takze wtedy, gdy
  generowanie nigdy sie nie zaczelo. Teraz komunikat nazywa etap:
  "Przekroczono czas oczekiwania na etapie: analizie zdjecia".
* Front zapamietuje ostatni etap zgloszony przez serwer (State.lastStatus).

NOWA RELACJA SPOJNOSCI (31 -> 32)
* Kazdy nieterminalny status zadania musi byc wymieniony w obsludze
  statusu. To jest ogolna postac usterki z analyzing: status bez drogi
  wyjscia jest slepa uliczka, a objawia sie cisza. Zbadanych statusow
  nieterminalnych: 5. Relacja ma licznik niewaznosci.

DOWODY MUTACYJNE
  | Mutacja                                       | Asercji upadlo |
  |-----------------------------------------------|----------------|
  | powrot do bramki zaleznej od orientacji       |              8 |
  | brak powiekszania, samo odrzucanie            |              5 |
  | gamma stosowana takze do zdjec dobrych        |              1 |
  | sufit gammy zdjety                            |              4 |
  | tablica gammy odwrocona                       |              2 |
  | wykrywanie zawieszonej analizy wylaczone      |              1 |
  | potok znowu odrzuca zamiast poprawiac         |              1 |
  | powrot podwojnego przekodowania               |              1 |
  | przywrocona slepa uliczka analyzing (R32)     |              1 |
  | wzorzec statusow przestaje pasowac (R32)      |              1 |

ASERCJA ZMIENIONA, NIE NAPRAWIONY KOD
* Zakladalem, ze kadr 640x800 wymaga powiekszenia, bo 640 jest ponizej
  dawnego progu 800 na szerokosci. Pomiar mowi co innego: krotszy bok
  640 >= 600, dluzszy 800 >= 800, pikseli 512 000 >= 480 000. Kadr
  spelnia wszystkie trzy warunki. Asercja zmieniona na 'ok'; to jest
  dokladnie ten przypadek, ktory stara bramka odrzucala bez powodu.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.18.0 | 3.18.1 |
  |------------------------|--------|--------|
  | zestawow               |     20 |     21 |
  | asercji zaliczonych    |    875 |    960 |
  | relacji spojnosci      |     31 |     32 |
  | plikow PHP po lincie   |    144 |    146 |
  | plikow JS po lincie    |     40 |     40 |


= 3.18.0 =

TYTULY I OPISY GMC: WARSTWA MODELU NAD SZABLONEM
* Zgloszenie: tytul ma zaczynac sie od frazy problemowej zwiazanej
  z pomieszczeniem i stylem, a nie od typu produktu. Szablon tego nie
  wymysli, bo nie zna problemow klientow. Nowa klasa
  PP_Insp_Gmc_Optimizer wola model instrukcja napisana przez wlasciciela.
* Kompozytor deterministyczny ZOSTAJE i jest droga wycofania. Wylaczenie
  opcji pp_insp_gmc_ai_enable przywraca zachowanie sprzed 3.18.0 w calosci.
* Domyslnie WYLACZONE. Wlaczenie kosztuje pieniadze i zmienia tresc
  widoczna w reklamie, wiec nie moze byc skutkiem aktualizacji.

  Instrukcja: co poszlo do modelu
  | Element                          | Znakow | Uwaga                        |
  |----------------------------------|--------|------------------------------|
  | instrukcja uzyta                 |    777 | reguly wlasciciela bez zmian |
  | blok "SCHEMA OUTPUTU" USUNIETY   |    432 | schemat wymusza API          |
  | blok wejscia na inspiracje       |    242 | Temat, Produkty, Cechy       |
  Schemat zostal usuniety z tekstu, bo response_schema wymusza ten sam
  ksztalt strukturalnie. Powtorzenie go slowami tylko rozcienczalo reguly.
  Dopisane jest JEDNO zdanie o paczce, bez ktorego model nie wie, ze ma
  odpowiedziec osobno dla kazdej inspiracji.

WSPOLNA WYSYLKA PO TRZY INSPIRACJE
* Instrukcja leci raz na paczke zamiast raz na inspiracje.

  Rachunek dla 300 inspiracji, zmierzony w tests/gmc-optimizer.test.php
  | Wariant     | Znakow wejscia | Zapytan |
  |-------------|----------------|---------|
  | pojedynczo  |        305 700 |     300 |
  | po trzy     |        150 300 |     100 |
  | roznica     |        155 400 |    -200 |
  Oszczednosc wynosi 50,8 procent i dotyczy WYLACZNIE wejscia. Wyjscie
  jest identyczne, bo tresci do napisania jest tyle samo.

* Wynik jest zapisywany razem z odciskiem tresci inspiracji, wiec model
  liczy kazda inspiracje RAZ, a nie raz na przebieg feedu. To rozstrzyga
  koszt mocniej niz paczka i zalatwia zarzut o powtarzalnosc tytulu:
  dopoki inspiracja sie nie zmieni, Google dostaje ten sam tytul i nie
  wszczyna ponownej weryfikacji.

BRAMKI, KTORE DECYDUJA, CZY ODPOWIEDZ MODELU W OGOLE WEJDZIE DO FEEDU
* Kazda odpowiedz przechodzi przez stray_numbers() z kompozytora, czyli
  TE SAMA bramke, ktora pilnuje szablonu. Liczba spoza rekordu faktow
  uniewaznia CALA odpowiedz. Nie poprawiamy jej, bo poprawianie zmyslenia
  to zgadywanie, a Merchant Center karze za to pozycja, przy powtorkach
  kontem.

  | Bramka                             | Odrzuca                        |
  |------------------------------------|--------------------------------|
  | dlugosc tytulu                     | powyzej 150 znakow             |
  | dlugosc opisu                      | ponizej 100 lub powyzej 150 slow |
  | slowa promocyjne                   | lista BANNED z kompozytora     |
  | wersaliki i wykrzyknik             | ciag 4+ wielkich liter, znak ! |
  | liczby spoza rekordu faktow        | zmyslona klasa, gwarancja, mm  |
  | liczba fraz niszowych              | poza zakresem 7-10             |
  | obcy insp_id                       | odpowiedz spoza zapytania      |
  | rozjazd pomieszczenia              | zamiana tresci w paczce        |

* Ostatnia bramka jest wlasnym ryzykiem paczki: model moze przypisac
  tresc nie tej inspiracji. Pomieszczenie jest jedynym polem, ktore
  dostaje wprost i ktorego nie ma prawa zmienic, wiec rozjazd znaczy
  zamiane. Zmierzone testem, nie zalozone.

* Odrzucenia trafiaja do dziennika widocznego na ekranie Nazwy i opisy
  GMC. Odrzucenie nie psuje feedu, bo na miejscu zostaje szablon, i
  wlasnie dlatego jest grozne: nic sie nie wywala, a tresc po cichu nie
  jest ta, o ktora wlasciciel prosil.

KARTA PRODUKTU NA STRONIE INSPIRACJI NIE MIALA PRZELACZNIKA
* Pomiar przed zmiana:
  | Co zmierzone                                     | Wynik |
  |--------------------------------------------------|-------|
  | przelacznikow w templates/single-inspiration.php |     0 |
  | odwolan do stanu included w buy.js               |    23 |
  | wywolan recomputeSinglePageTotal                 |     0 |
  | zawartosc singlePageControllers                  | zawsze puste |
  Stan skladu istnial wylacznie w modalu. Cena pod lista nie zmieniala
  sie nigdy, bo jej jedyny pisarz byl martwym kodem.

* Sklad zestawu przeniesiony do DOM: atrybut data-included na
  .pp-buy-wrap. Czytaja go cztery miejsca, ktore wczesniej nie mialy
  wspolnego zrodla: cena pod lista, licznik skladu, modal zestawu
  i pasek mobilny.
* Martwa sciezka usunieta w calosci: singlePageControllers,
  recomputeSinglePageTotal i submitSinglePageSet.
* Cena pod lista liczy sie z pozycji WCHODZACYCH do zestawu. Wczesniej
  sumowala wszystkie produkty, takze niedostepne, ktore startuja
  wylaczone: klient widzial kwote za cztery pozycje przy przycisku
  dodajacym trzy.
* Wezel ceny renderuje sie zawsze, takze przy zerze. Wczesniej znikal
  i po wlaczeniu produktu nie bylo dokad wpisac kwoty.
* Niedostepnosc bije wybor klienta: podmiana atrybutu w narzedziach
  przegladarki nie wciagnie do koszyka pozycji, ktorej nie da sie kupic.

RYSUNEK POKOJU W KONFIGURATORZE BYL WSKAZNIKIEM, NIE STEROWANIEM
* Pomiar przed zmiana: wielokaty mialy poprawne klasy is-active
  odbijajace State.selectedSurfaces i ZERO nasluchow.
* Piec wielokatow dostalo data-surface, role button, tabindex i osobne
  nazwy dla czytnika ekranu. Klikniecie i klawiatura przelaczaja wybor.
* Model dziedziny ma TRZY powierzchnie, a rysunek PIEC wielokatow: trzy
  sciany niosa to samo data-surface="wall". Liczba scian ma osobne
  sterowanie, wiec rysunek jej nie dubluje.
* Ostatniej powierzchni nie da sie odznaczyc: pusty wybor blokowal
  kreator bez wyjscia poza odswiezeniem strony.
* Ozdobne kreski scian dostaly pointer-events: none. Leza NA wielokatach
  jako rodzenstwo, a wielokat SVG nie ma dzieci, wiec bez tego polykaly
  klikniecie w sciane pod spodem. Test sprawdza obie strony naraz, bo
  sama asercja na JS przechodzilaby przy zepsutym CSS.

NOWE SPRAWDZENIA SPOJNOSCI (29 -> 31 relacji)
* Kazdy skrypt i arkusz wpisany do kolejki istnieje na dysku. Bledna
  sciezka w wp_enqueue_script nie rzuca bledem PHP: przegladarka dostaje
  404 i caly modul frontowy przestaje dzialac bez sladu w dzienniku.
  Zbadanych wpisow: 83.
* Kazda klasa startowana w bootstrapie ma plik na liscie require.
  Zbadanych startow: 25. Nazwa pliku NIE wynika z nazwy klasy
  (PP_RV_Guard mieszka w class-pp-room-visualizer-guard.php), wiec
  sprawdzenie szuka pliku deklarujacego klase, a potem pyta o liste.
* Obie relacje maja licznik niewaznosci: zero zbadanych zglasza sie jako
  blad, a nie jako zaliczenie.

DOWODY MUTACYJNE
  | Mutacja                                   | Asercji upadlo |
  |-------------------------------------------|----------------|
  | klikniecie w rysunek nic nie robi         |              6 |
  | ostatnia powierzchnia daje sie usunac     |              3 |
  | ozdobnik traci pointer-events             |              1 |
  | trzy sciany dostaja rozne data-surface    |              2 |
  | wykluczenie produktu przestaje dzialac    |              8 |
  | niedostepnosc przestaje bic wybor klienta |              2 |
  | brak ceny znowu liczy sie jako zero       |              2 |
  | zaokraglenie groszy znika                 |              2 |
  | przelacznik znika z szablonu              |              1 |
  | modal znowu bierze wszystkie produkty     |              1 |
  | bramka zmyslonych liczb wylaczona         |              2 |
  | limit dlugosci tytulu wylaczony           |              1 |
  | kontrola pomieszczenia wylaczona          |              1 |
  | odcisk przestaje widziec styl             |              2 |
  | zakres dlugosci opisu wylaczony           |              2 |
  | zakaz krzyku wylaczony                    |              2 |
  | warstwa modelu wlaczona domyslnie         |              1 |
  | skrypt w kolejce wskazuje nieistniejacy plik |           1 |
  | startowana klasa poza lista require       |              1 |
  | wzorzec kolejki przestaje pasowac         |              1 |

LUKA W TESCIE ZNALEZIONA WLASNYM DOWODEM MUTACYJNYM
* Asercja "domyslnie warstwa jest wylaczona" stala PRZED ustawieniem
  klucza API i przechodzila z powodu jego braku, a nie z powodu domyslnej
  wartosci opcji: podmiana domyslnej '0' na '1' nie wywolala ani jednego
  bledu. Asercja przeniesiona za ustawienie klucza i rozbita na dwie.
  Po poprawce ta sama mutacja wywala 1 asercje.

STAN ZESTAWOW TESTOWYCH
  | Miara                  | 3.17.0 | 3.18.0 |
  |------------------------|--------|--------|
  | zestawow               |     16 |     20 |
  | asercji zaliczonych    |    702 |    875 |
  | relacji spojnosci      |     29 |     31 |
  | plikow PHP po lincie   |    141 |    144 |
  | plikow JS po lincie    |     38 |     40 |


= 3.17.0 =

TYTULY I OPISY GMC REKLAMOWALY MODEL ZAMIAST POMIESZCZENIA I STYLU
* Zmierzone na rekordzie "Biuro Japandi". Tytul przed zmiana:
  "Panele winylowe do biura + listwy poliuretanowe - zestaw 17,5 m2,
  Lion, DB-5761, 2.5 mm, Japandi". W widocznych 70 znakach klient dostawal
  marke Lion, a slowo Japandi lezalo POZA obcieciem, za kodem DB-5761.
* Po zmianie: "Panele winylowe do biura w stylu japandi - zestaw 17,5 m2,
  listwy poliuretanowe". Styl jest w czesci widocznej, tytul skrocil sie
  z 96 do 79 znakow.
* Z tytulu wylecialy: model (symbol katalogowy, ktorego klient nie zna),
  grubosc i klasa (parametry techniczne, nie frazy zakupowe) oraz marka,
  bo feed ustawia ja OSOBNYM atrybutem brand i w tytule byla powtorzeniem.
* Na ich miejsce weszly: kolor oraz najlepsza fraza dlugiego ogona
  z generatora fraz sprzedazowych. Jedna, nie lista, bo kilka fraz sklejonych
  przecinkami to dla Google upychanie slow kluczowych.

OPIS
* Drugi akapit byl zrzutem karty katalogowej: "Parametry elementu wiodacego
  - Producent: Lion. Symbol: DB-5761. Grubosc: 2.5 mm...", a zdanie o stylu
  stalo na samym koncu. Teraz drugi akapit mowi o pomieszczeniu i stylu,
  a parametry zostaly zawezone do cech, ktore klient realnie wpisuje:
  wodoodpornosc, ogrzewanie podlogowe, material, klasa, warstwa, podklad.
* Producent, symbol, kolekcja i grubosc usuniete z akapitu parametrow.
* Zdanie zamykajace przestalo powtarzac styl, bo ten przeniosl sie wyzej.

FRAZY ZAPASOWE
* Kolejnosc odwrocona. Wczesniej lista zaczynala sie od "typ do pomieszczenia"
  i "typ do pomieszczenia cena", styl byl ostatni, koloru nie bylo wcale.
  Teraz pierwsze ida frazy o pomieszczeniu i stylu, czyli po to, po co
  powstaja inspiracje, a warianty oparte na samym typie produktu zostaja
  jako uzupelnienie, bo te obsluguje juz karta produktu w katalogu.

ASERCJA ZMIENIONA, NIE NAPRAWIONY KOD
* Asercja "marka i model sa, ale ZA granica widocznosci" wymagala, zeby
  symbol PAN000358 ZNAJDOWAL sie w tytule, czyli utrwalala odrzucone
  zachowanie. Zastapiona odwrotnoscia: symbol nie moze pojawic sie w tytule
  nigdzie. Dolozone dwie asercje: grubosc nie wystepuje w tytule oraz styl
  miesci sie w widocznych 70 znakach.

= 3.16.1 =

SZYBKI PODGLAD W GALERII POKAZYWAL 1 SZTUKE KAZDEGO PRODUKTU
* Objaw zmierzony na zrzucie: suma zestawu 331,31 zl to dokladnie 31,79 plus
  112,26 plus 187,26, czyli po jednej sztuce kazdej pozycji, niezaleznie od
  tego, co policzyl system i co ustawil klient na ekranie wyniku.
* Przyczyna: widzet ilosci w galerii mial value rowne 1 wpisane na sztywno,
  a wynik kalkulatora ani zmiana klienta nie mialy gdzie przetrwac miedzy
  ekranem wyniku a galeria. Nie bylo kolumny na ten stan.
* Nowa kolumna quantities_json w tabeli galerii, wersja schematu podniesiona
  z 1.4.0 na 1.5.0, wiec dbDelta doda ja przy pierwszym wejsciu do panelu.
* Nowy przycisk "Zapisz ilosci" na ekranie wyniku, cichy i obrysowy, nie drugie
  wezwanie do dzialania. Zapis jest JAWNY: automatyczny przy kazdym kliknieciu
  w stepper wysylalby kilkanascie zadan podczas dochodzenia do liczby i nie
  dalby sie cofnac. Zapisywane jest takze WYLACZENIE pozycji, bo to decyzja
  rownie warta zapamietania jak zmiana liczby.
* Pierwszenstwo zrodel rozstrzyga SERWER, w jednym miejscu: ilosc zapisana
  przez klienta, potem wyliczona przez system, potem jedynka. Gdyby wybierala
  przegladarka, ta sama regula istnialaby osobno na dwoch ekranach, a dwie
  redakcje tej samej reguly rozjezdzaja sie w tym projekcie regularnie.
* Zapis uniewaznia pamiec podreczna galerii, inaczej podglad przez kilka minut
  pokazywalby stan sprzed zapisu i klient uznalby, ze przycisk nie dziala.
* Nowy zestaw tests/start-qty.test.php, 18 asercji.

POPRAWKA ZNALEZIONA PRZEZ TEN ZESTAW
* absint(-5) zwraca 5, wiec ujemny identyfikator produktu cicho zamienial sie
  w prawidlowy i wskazywal cudzy produkt. Bramka sanujaca zamiast odrzucajacej
  przestaje byc bramka przy pierwszym nowym wywolaniu. Zamienione na odrzucenie.

= 3.16.0 =

LICZBA SCIAN WYBIERANA PRZEZ KLIENTA
* Nowy wybor w kreatorze, widoczny po zaznaczeniu sciany: na ilu scianach ma
  sie znalezc produkt scienny. Cztery przyciski, prog dotykowy 44 px.
* Wybor idzie w TRZY miejsca naraz, nie w jedno: do promptu obrazu, zeby model
  pokryl tyle scian, ile klient wybral; do wyliczenia ilosci; oraz do opisu
  podstawy pod wynikiem, zeby bylo widac, skad liczba.
* Do tej pory KAZDY produkt na metry biezace byl liczony z pelnego obwodu
  pomieszczenia. Przy pokoju 20 m2 obwod wynosi okolo 18 m, wiec klient chcacy
  lamele na jednej scianie widzial wycene na cztery. Zmierzone po naprawie:
  1 sciana 4,5 mb, 2 sciany 9,1 mb, 3 sciany 13,6 mb, 4 sciany 18,1 mb.
* Rozroznienie jest rzeczowe. Listwa przypodlogowa i przysufitowa biegna wokol
  calego pomieszczenia, wiec ich ilosc NIE zalezy od liczby scian; sprawdzone
  osobna asercja, 17,2 mb przy jednej i przy czterech scianach.
* Podzial obwodu przez cztery zaklada pomieszczenie zblizone do prostokata.
  To zalozenie, nie pomiar, i jest wypisane w opisie podstawy.

LISTWY POKAZYWALY 1 SZT. ZAMIAST WYLICZONEJ ILOSCI
* Przyczyna: estymator czytal dlugosc sztuki wylacznie z pola package_size,
  a motyw dla produktow na metry biezace podaje ja w polu item_length.
  Dla listwy 2,4 m package_size wynosi zero, wiec liczba sztuk wychodzila zero,
  pozycja dostawala oznaczenie niepoliczalnej i wypadala z tabeli, a karta
  produktu spadala do jedynki. Widac to bylo na zrzucie z produkcji: podloga
  12 paczek, obie listwy po 1 szt., mimo ze karta pisala "sztuka 2,4 m".
* Kolejnosc odczytu odwrocona: najpierw item_length, potem package_size.

INSTRUKCJA FOTOGRAFOWANIA PRZED WGRANIEM
* Cztery zasady z ikonami plus jedna linia z tym, czego unikac. Kazda zasada
  odpowiada realnemu kodowi odrzucenia zdjecia albo bezposrednio poprawia
  szacunek metrazu, ktory potrzebuje przedmiotu odniesienia o znanej wielkosci.
* Instrukcja stoi PRZED wgraniem, bo po odrzuceniu klient stracil juz podejscie.

POZOSTALE
* Reczne przeliczenie metrazu zachowuje wybrana liczbe scian. Bez tego cicho
  wracaloby do jednej sciany i kasowalo decyzje z kreatora.
* Naprawiony blad wprowadzony przy tej zmianie: galaz else czyszczaca styl
  sciany przykleila sie do nowego warunku i kasowala styl, gdy liczba scian
  byla spoza zakresu. Lint tego nie widzi, bo skladnia jest poprawna.
* Nowy zestaw tests/wall-count.test.php, 15 asercji, uruchamiajacy prawdziwy
  estymator poza WordPressem.

= 3.15.2 =

WYSZUKIWARKA UMIERALA NA STALE PO PUSTYM WYNIKU
* Objaw zgloszony przez wlasciciela: po wybraniu kilku produktow i powrocie do
  panelu wyboru wpisanie frazy nie pokazywalo nic, a pozniej produkty nie
  wracaly juz wcale, takze po wyczyszczeniu kryteriow.
* Na zrzucie widac dowod: filtr ceny pokazywal "10 - 0 zl", czyli minimum
  wyzsze od maksimum. Taki przedzial nie pasuje do zadnego produktu.
* Przyczyna byla DWUCZLONOWA i zaden czlon osobno nie wygladal na wade.
  Serwer dla pustego wyniku zwracal WYMYSLONY zakres 0 i 0. Przegladarka
  przycinala kazdy koniec tylko w jedna strone: dolna cene podnosila, gdy
  spadla ponizej granicy, gorna obnizala, gdy przekroczyla granice, i nic nie
  pilnowalo, zeby dolna nie przekroczyla gornej. Z pary 10 do 500 robilo sie
  10 do 0, a poniewaz kolejne zapytanie tez wracalo puste, stan sam sie
  utrwalal.
* Naprawione po obu stronach. Serwer zwraca teraz null, czyli "nie wiem",
  a nie zero, takze gdy dane daja pare odwrocona. Przegladarka przepuszcza
  zakres przez jedna czysta funkcje, ktora nigdy nie odwraca pary i nie
  przyjmuje braku wiedzy za granice.
* Przy pustym wyniku pojawia sie przycisk "Wyczysc filtry" jako druga linia
  obrony, bo pusta lista moze wziac sie takze z frazy albo kategorii.
* Nowy zestaw tests/price-range.test.js, 22 asercje, w tym niezmiennik na
  3000 krokach z co trzecim wynikiem pustym.

FILTR KATEGORII
* Usunieta kategoria "Akcesoria i montaz" (id 6918, 30 produktow), bo nie da
  sie jej zwizualizowac we wnetrzu. Dodane "Listwy scienne" (id 6910,
  108 produktow).
* Pusta konfiguracja znaczyla dotad "pokaz WSZYSTKIE kategorie glowne sklepu",
  stad akcesoria w filtrze. Teraz domyslna lista jest jawna i filtrowalna
  hakiem pp_rv_visualizable_cats, a wpis w ustawieniach nadal ma pierwszenstwo.
* Identyfikator kategorii jest brany DOSLOWNIE. Wczesniej kazda podkategoria
  byla awansowana do korzenia, wiec wskazanie "Listwy scienne" dawaloby kafel
  "Sztukateria wewnetrzna", czyli 402 produkty zamiast 108.
* Kafle filtra i bramka wyszukiwania czytaja teraz JEDNA liste. Byly to dwie
  osobne listy, ktore rozjezdzaly sie przy kazdej konfiguracji wskazujacej
  podkategorie. Pilnuje tego nowa kontrola w autotescie spojnosci.

= 3.15.1 =

DWA ZGLOSZENIA Z DZIENNIKA PRODUKCJI.

TABELA ZDARZEN NIE POWSTAWALA
* Dziennik notowal przy KAZDYM zadaniu wizualizacji: Table 'wp_pp_insp_events'
  doesn't exist, sciezka PP_Insp_Viz_Bridge::on_started do
  PP_Insp_Event_Ledger::record. Skutek powazniejszy niz sam wpis: cala warstwa
  zdarzen nie zapisywala niczego, wiec wyzwalacze i rekomendacje pracowaly na
  pustym zbiorze i nie mialy jak tego zglosic.
* Przyczyna: rejestr zdarzen byl JEDYNA z czterech klas tworzacych tabele bez
  instalatora na admin_init. Jego install() wolaly wylacznie hooki aktywacji,
  a WordPress NIE uruchamia hooka aktywacji przy podmianie wtyczki nowsza
  paczka. Trzy siostrzane klasy mialy te linie od dawna.
* Druga polowa wady, naprawiona we wszystkich czterech klasach: bramka
  instalatora ufala samej opcji wersji. Gdy opcja jest zapisana, a tabela
  zniknela, install() odmawial pracy na zawsze. Teraz rozstrzyga stan bazy.
* Dwie nowe kontrole w autotescie spojnosci, widoczne w panelu: kazda klasa
  tworzaca tabele ma instalator na admin_init, oraz bramka instalatora
  sprawdza istnienie tabeli.

PACZKA
* Archiwum nie zawieralo wpisow katalogowych (0 wobec 21 w paczce, ktora
  wgrala sie czysto). Przywrocone. Nie jest ustalone, czy to bylo przyczyna
  ostrzezenia fileperms przy aktualizacji; roznica zostala usunieta, bo byla
  jedyna roznica strukturalna miedzy paczkami.

= 3.15.0 =

EKRAN WYNIKU, PASEK POSTEPU I ILOSCI Z KALKULATORA. Trzy zgloszenia wlasciciela,
20 wad zmierzonych w kodzie i na produkcji.

PASEK POSTEPU
* Szerokosc paska zapisywaly TRZY niezalezne zrodla naraz: symulacja lokalna
  co 500 ms, odpytywanie serwera co 3000 ms i postep wysylki pliku. Straznik
  monotonicznosci pilnowal pola jednego z nich, wiec nie obejmowal pozostalych.
  Zmierzone wykonaniem w 60 sekundach: 83 zapisy, 12 cofniec, najwieksze
  o 58,67 punktu. Zostal JEDEN punkt zapisu z czysta funkcja decyzyjna.
* Plynne dojscie do celu robi krokomierz wewnatrz tego punktu, wiec skok
  serwera z 10 na 75 przestaje byc przeskokiem.
* Nowa proba zeruje pasek JAWNIE i mowi o tym wprost. Spadek procentu nie jest
  juz powodem zerowania, bo nie odroznia ponowienia od stanu poczatkowego.
* Serwer zaczal MIERZYC czas: kolumna generation_started_at, pole
  elapsed_seconds i mediana z zakonczonych generacji. Zdanie "To moze potrwac
  30-90 sekund" zniklo, bo nic go nie popieralo.

EKRAN WYNIKU
* Sufit kontenera podniesiony z 960 px do 1800 px na tym jednym ekranie.
  Wizualizacja rosnie z okolo 500 px do okolo 1230 px przy ekranie 1920.
* Zostalo JEDNO wezwanie do zakupu. Dwa przyciski o roznych ladunkach sumowaly
  ilosci w koszyku, dajac 13 paczek zamiast 12.
* Produkty stoja nad przyciskiem zakupu.
* Pasek krokow 1-2-3 znika na wyniku, bo nie ma stamtad powrotu wstecz.
* Na telefonie dolne menu motywu jest ukrywane na tym ekranie, a pasek zakupu
  dostal rezerwe na obszar bezpieczny. Wtyczka nie miala dotad ZADNEGO
  mechanizmu dotykajacego elementow motywu.
* Rozstrzygniety konflikt specyficznosci, przez ktory rzad przyciskow
  pomocniczych byl na telefonie widoczny wbrew intencji autora.

ILOSCI
* Karta produktu startowala od literalu 1 i nigdy nie czytala wyniku szacunku,
  wiec tabela mowila 12 opakowan, a karta 1 paczke. Teraz obie liczby maja
  jedno zrodlo.
* Pole ilosci przestalo byc tylko do odczytu. Dojscie do 12 paczek kosztowalo
  jedenascie klikniec w plus.
* Zapas na docinki mial dwie wartosci: 5 procent w backendzie i 10 procent
  w przegladarce, bo klucz defaultWaste nie byl lokalizowany do wizualizatora.
  Zostala jedna, ta z ustawien sklepu.
* Przycisk Przelicz podmienial trzy napisy i nie ruszal ani tabeli, ani kart,
  ani koszyka, a w czynnej sciezce liczyl zawsze zero. Teraz przelicza wszystko
  na serwerze ta sama funkcja, ktora liczy szacunek automatyczny.

KONTROLE
* Dwie nowe relacje w autotescie spojnosci, widoczne w panelu: jeden pisarz
  szerokosci paska, jedna sciezka dodania do koszyka na ekranie wyniku.
* Nowy zestaw tests/progress-bar.test.js, 23 asercje, w tym niezmiennik
  monotonicznosci na 6000 krokach.

= 3.14.1 =

HIGIENA WERSJI I SYMBOLI. Cztery usterki tej samej rodziny: odwolanie do symbolu
albo wersji, ktore w chwili uzycia nie jest tym, czego kod oczekuje.
* Dyspozytor widokow kreatora wolal metody renderAuth i renderConsent, usuniete
  przy rozbiciu w 3.14.0. Nieosiagalne, bo nic nie ustawialo tych widokow, ale
  pierwsze ustawienie dawaloby TypeError zamiast ekranu zastepczego.
* Changelog w tym pliku stal na 3.6.5 przy Stable tag 3.14.0. Uzupelniony o 26
  wydan od 3.7.0.
* PP_RV_VERSION o wartosci 1.3.3 byla kluczem cache dla 21 kolejkowan i zarazem
  bramka schematu bazy. Jako klucz nie robila nic: 3.14.0 przepisalo caly modul
  kreatora, a pliki nadal wychodzily z ?ver=1.3.3. Role rozdzielone, kluczem jest
  PP_INSP_VERSION, bramka schematu zostaje przy 1.3.3.
* Rozruch room-viz-core.js wolal init() z linii 1461, a ten siegal po ZoomPreview
  deklarowany const dopiero w linii 1519. Przy gotowym dokumencie dawalo to
  ReferenceError i utrate wszystkich 17 eksportow modulu. Nie strzelalo tylko
  dlatego, ze skrypty ida bez atrybutu defer. Rozruch przeniesiony na koniec.
* Piec nowych kontroli z dowodem mutacyjnym: rownosc zbioru galezi dyspozytora
  ze zbiorem widokow, zgodnosc czterech znacznikow wersji, zakaz zamrozonego
  klucza cache w kolejkowaniu, rozwiazywalnosc wywolan this.X() oraz wczytanie
  rdzenia przy gotowym dokumencie. Trzy pierwsze widoczne w panelu.

= 3.14.0 =
* Rozbicie kreatora wizualizacji. room-viz-inline.js mial 6299 linii w jednym
  domknieciu; podzielony na siedem plikow, najwiekszy 1967 linii. Ani jedna linia
  logiki nie zostala przepisana, rozdzielacz wycina zakresy wierszy doslownie.
  Usuniety martwy renderAuth i renderConsent, lacznie 359 linii.

= 3.13.2 =
* Fraza glowna to tytul inspiracji, a nie sklejka kategorii produktow skladowych.

= 3.13.1 =
* Dwie usterki wykryte przebiegiem na sucho na prawdziwej aranzacji, ktorych nie
  pokazal zaden test jednostkowy.

= 3.13.0 =
* Przebudowa nazw i opisow zestawow pod wymagania Google Merchant Center.

= 3.12.4 =
* Feed produktowy nie dzialal w ogole: produkt-cien byl tworzony jako ukryty
  w katalogu, a wtyczka Google for WooCommerce odmawia wtedy synchronizacji.

= 3.12.3 =
* Trzy zgloszenia z produkcji, w tym systemowe okno przy ocenie inspiracji
  bez konta.

= 3.12.2 =
* Przyklejony przycisk zakupu na telefonie chowal sie pod dolnym menu motywu.

= 3.12.1 =
* Kategorie zestawow wyprowadzane ze skladu zamiast jednej kategorii dla
  wszystkich aranzacji.

= 3.12.0 =
* Rabat naliczany samoczynnie, bez przepisywania kodu przez klienta.

= 3.11.1 =
* Naprawa regresji z 3.11.0: kazda ukryta strona zaplecza otwierana adresem
  admin.php?page=... konczyla sie bledem 403.

= 3.11.0 =
* Porzadek menu zaplecza: dziesiec pozycji zeszlo do czterech grup, bez zmiany
  technologii i bez zmiany ani jednego adresu strony.

= 3.10.2 =
* Domkniecie dwoch deficytow wskazanych przy ocenie wydania 3.10.1.

= 3.10.1 =
* Audyt spojnosci. Wydanie bez nowych funkcji, same defekty relacji miedzy
  modulami, znalezione sondami i potwierdzone na kodzie.

= 3.10.0 =
* Trzy kanaly kontaktu, atrybucja przychodu i realny koszyk. Siedem faz planu.

= 3.9.1 =
* Ulubione przezywaja wyczyszczenie przegladarki, a szybka rejestracja kosztuje
  klienta jedno pole zamiast calego formularza.

= 3.9.0 =
* Konfigurator regul: kto, w jakiej sytuacji i co wtedy robimy, wykonywane
  zarowno wobec klienta obecnego na stronie, jak i nieobecnego.

= 3.8.1 =
* Ustalenie, dlaczego warstwa gradingu nie dzialala, oraz autotest, ktory teraz
  jej pilnuje i zglasza awarie w panelu.

= 3.8.0 =
* Zmiany wybrane pomiarem na zywych generacjach: siedem generacji, jedna scena
  bazowa, po jednej zmiennej na wariant. Wdrozone tylko to, co wygralo pomiarem.

= 3.7.7 =
* Ujecie detalu nie moze kadrowac obszaru bez produktu; przy oprawach LED scena
  przechodzi na pore wieczorna, inaczej efekt swiatla jest nieczytelny.

= 3.7.6 =
* Najechanie na pin przy gornej krawedzi przewijalo strone, przez co opis
  produktu nigdy sie nie pokazywal. Przewijanie zostaje wylacznie przy klinieciu.

= 3.7.5 =
* Ujecie B: przyczyna ustalona eksperymentem. Obraz referencyjny zostaje,
  bo bez niego model wymysla inny pokoj; usterka lezala w dlugosci promptu.

= 3.7.4 =
* Ujecie B i oznaczanie produktow po zgloszeniu z produkcji.

= 3.7.3 =
* Silnik stref zakazanych dla pinow, obejmujacy kazda nakladke nad obrazem,
  a nie jeden zgloszony element.

= 3.7.2 =
* Przelacznik ujec mial piec definicji w czterech arkuszach, kazda z pelnym
  zestawem !important. Skonsolidowany do jednego arkusza, stare bloki usuniete.

= 3.7.1 =
* Ujecie B pokazywalo ten sam pokoj odbity lustrzanie zamiast z innego miejsca.

= 3.7.0 =
* Warstwa fotograficzna: trzy etapy o rozdzielonej odpowiedzialnosci, czyli
  zamowienie ekspozycji, wywolanie obrazu i bramka akceptacji.

PELNY OPIS WYDAN OD 3.7.0, z tabelami pomiarow, odrzuconymi wariantami i lista
tego, czego nie zweryfikowano, znajduje sie w pliku docs/ZMIANY-3.7.0.md.

= 3.6.5 =

UJECIE B — ODBICIE LUSTRZANE ZAMIAST NOWEGO UJECIA (regresja z 3.6.4)
* Wersja 3.6.4 konczyla przydzial punktu widzenia zdaniem: "keep the same walls,
  the same objects and the same materials, but RE-DERIVE WHICH SIDE OF THIS FRAME
  each of them falls on". To jest doslownie polecenie "te same obiekty, inne strony",
  a najtanszym sposobem jego wykonania jest ODBICIE LUSTRZANE. Model wykonal je
  poprawnie — wadliwe bylo polecenie, nie wykonanie.
* Efekt na produkcji: ujecie B bylo idealnym odbiciem ujecia A. Sciana z lamelami
  przeskakiwala z lewej na prawa, okno z prawej na lewa, ta sama lampa, ten sam szlafrok
  na krzesle, ta sama roslina — wszystko przerzucone w poziomie.

DLACZEGO NOWE SFORMULOWANIE UNIEMOZLIWIA ODBICIE
* Przestajemy mowic o STRONACH KADRU, zaczynamy o RELACJI KAMERY DO OKNA. Okno jest
  jednym, nieruchomym obiektem: albo jest w kadrze, albo za plecami fotografa, albo
  na wprost. Lustro tego nie zmienia — zmienia wylacznie strone.
* Kazdy z czterech wariantow okresla teraz, GDZIE jest okno wzgledem obiektywu I CO
  Z TEGO WYNIKA DLA SWIATLA:
  - przy scianie okiennej, okno za plecami: okna NIE MA w kadrze, cienie uciekaja
    od obiektywu w glab pokoju,
  - z drzwi na wprost okna: kadr pod swiatlo, tkanina zaslon swieci od tylu,
    przedmioty czytaja sie czesciowo jako sylwetki,
  - z narozy najdalszego od okna: okno widziane pod ostrym katem, waskie w kadrze,
    dlugi klin swiatla przez podloge w strone obiektywu,
  - od sciany naprzeciw okna: okno przed obiektywem, ale z boku — polowa pokoju
    jasna, polowa schodzi w cien.
* Jawny zakaz odbicia z DIAGNOSTYKA i uzasadnieniem fizycznym: pokoj ma JEDNO okno
  na JEDNEJ scianie, wiec odbicie przenioslo by je na sciane, ktorej nie zajmuje,
  i odwrocilo kierunek swiatla dziennego — co o tej samej porze dnia jest niemozliwe.
* Wymog wymiany tresci w obie strony: cos, czego nie ma w referencji, MUSI sie pojawic,
  a cos, co referencja pokazuje, MUSI wypasc poza kadr. Samo dodanie nie wystarczy,
  bo odbicie tez "dodaje" (po drugiej stronie).
* Ten sam zakaz dopisany do instrukcji trybu reframe w vision.php, czyli tam, gdzie
  do modelu trafia obraz referencyjny — bo to obecnosc obrazu najsilniej zacheca do
  transformacji minimalnej, jaka jest odbicie.

= 3.6.4 =

UJECIE B — WLASCIWA PRZYCZYNA POWIELANIA KADRU (opis sceny, nie sila instrukcji)
* Opis sceny definiowal uklad pomieszczenia przez LEWO i PRAWO: "feature wall on the
  left, window on the right, bed off-centre to the left, bedside table on the right".
  Te relacje sa prawdziwe TYLKO z jednej pozycji obserwatora. Model odtwarzajac je
  MUSIAL wrocic dokladnie na pozycje ujecia A — inaczej opis przestawalby sie zgadzac.
* Opis sceny jest wysylany PIERWSZY, jest najdluzszy i najbardziej konkretny, wiec
  wygrywal z kazda instrukcja zmiany kadru. Dokladanie mocniejszych polecen nic nie
  dawalo, bo problem nie lezal w ich sile.
* Model jezykowy MUSI teraz nazywac sciany PRZEZ FUNKCJE: "the window wall",
  "the door wall", "the feature wall", "the wall facing the window". Kierunki wzgledne
  sa zakazane wprost, z uzasadnieniem w tresci instrukcji.
* view_b_vantage() przepisane na odniesienia funkcyjne. Poprzednia wersja mowila
  "stan tam, gdzie byla LEWA sciana" — co wygladalo na instrukcje bezwzgledna, ale
  odwolywalo sie do punktu, ktory wlasnie zmieniamy. Cztery warianty operuja teraz
  scianami: od okna w glab, od drzwi wzdluz pokoju, z narozy najdalszego od okna,
  od sciany naprzeciw okna.
* Dla opisow juz zapisanych dodano jawne rozstrzygniecie: kazde "left"/"right" w opisie
  dotyczy ujecia ustanawiajacego, a dla tego ujecia strone kadru nalezy wyprowadzic
  na nowo.

KAMERA — PIECIU WLASCICIELI, DWIE SPRZECZNE PROJEKCJE
* W ujeciu B polecenia kamerze wydawalo PIEC zrodel: dyrektywa redakcyjna profilu,
  wytyczne powierzchni, dyrektywa optyki, kontrakt kompozycji i przydzial punktu
  widzenia.
* Dwa byly wprost sprzeczne: wytyczne kazaly celowac WZDLUZ jednej sciany i w gore
  (rzut jednopunktowy), a dyrektywa optyki W NAROZNIK z dwiema scianami uciekajacymi
  po skosie (rzut dwupunktowy). Model rozstrzygal to losowo.
* Nowa metoda strip_camera_language() zdejmuje z blokow redakcyjnych zdania mowiace,
  JAK ustawic aparat, zostawiajac te, ktore mowia, CO pokazac. Kryterium zawezone:
  zdanie musi zawierac slowo o aparacie ORAZ konkretny parametr lub nazwe ujecia —
  samo "frame" nie wystarczy.
* Zmierzone na rzeczywistych blokach z logu: wytyczne powierzchni 470 -> 280 znakow,
  dyrektywa redakcyjna 364 -> 163 znaki, bez utraty tresci o zawartosci kadru.

= 3.6.3 =

OPTYMALIZACJA DLUGOSCI PROMPTU — ZASADA JEDNEGO WLASCICIELA
Audyt na rzeczywistym logu generacji: 23 bloki, 9361 znakow na ujecie, z czego
6213 znakow w obszarach majacych WIELU wlascicieli tego samego zagadnienia.
Nawet spojny prompt tej dlugosci rozmywa priorytety — model wazy wszystko po rowno.

Kazde zagadnienie ma teraz DOKLADNIE JEDNEGO wlasciciela:
* STYL — Modul 1. Usunieto osobna linie "Interior style: ...", ktora powtarzala
  te informacje, w dodatku po polsku.
* OSWIETLENIE — light_contract(). Wczesniej szly TRZY opisy naraz: etykieta nastroju,
  kontrakt swiatla i klauzula w ograniczeniach realizmu. Ponad 1000 znakow o tej samej
  rzeczy, czesciowo sprzecznie.
* PALETA — blok COLOUR SCHEME. Usunieto poprzedzajace go zdanie streszczajace te same
  informacje bez kodu RAL. Streszczenie przed pelna wersja tylko rozcienczalo polecenie.
* ZAKAZY — jedna scalona lista. BANNED i "Avoid" mialy duze pokrycie
  ("no plastic-looking surfaces" vs "plastic or waxy surfaces", zakaz tekstu i znaku
  wodnego w obu). Nowa metoda merge_negatives() porownuje pozycje po rdzeniu, wiec
  rozpoznaje ten sam zakaz zapisany innym szykiem.
* BLOKADA TOZSAMOSCI — usunieto ogolna preambule bloku LOCKED ELEMENTS, ktora
  powtarzala tresc SCENE FIDELITY LOCK. Wartosc niesie wylacznie konkretna lista
  elementow od modelu jezykowego.

SKROCENIE WLASNYCH KONTRAKTOW
* micro_detail_contract, composition_contract i realism_anchors byly napisane
  rozwlekle. Ten sam sens, zwiezlej.
* W ujeciu detalu pominieto kotwice realizmu — staging_contract() opisuje juz to samo.

DEDUPLIKACJA ZDANIOWA JAKO SIEC BEZPIECZENSTWA
* Nowa metoda compact_prompt() usuwa powtorzone zdania z gotowego promptu.
* To NIE jest glowny mechanizm — duplikaty usuwamy u zrodla. Ta metoda lapie to,
  czego u zrodla usunac nie wolno: tresci wpisane przez administratora w Studio
  Workflow i dyrektywy presetow branzowych, ktore moga powtarzac to, co kompozytor
  juz powiedzial.
* Porownanie po znormalizowanym rdzeniu zdania, wiec lapie takze zapis rozniacy sie
  wielkoscia liter i interpunkcja. Zdania krotsze niz 45 znakow zostaja nietkniete —
  bywaja naglowkami sekcji.

WYNIK: 9361 -> okolo 6700 znakow na ujecie, redukcja okolo 28% bez utraty tresci,
plus dodatkowa redukcja z deduplikacji zaleznie od zawartosci pol administratora.

= 3.6.2 =

DEFEKT KRYTYCZNY — DWIE SPRZECZNE PALETY W JEDNYM PROMPCIE
* Kazde ujecie otrzymywalo DWA bloki "COLOUR SCHEME - MANDATORY, NOT A SUGGESTION"
  z ROZNYMI kolorami scian: ujecie A rdzawy RAL 8023 oraz antracyt RAL 7016,
  ujecie B rdzawy oraz bez RAL 1019, ujecie C rdzawy oraz petrol RAL 5024.
* Przyczyna: get_color_accent_instruction( 'ai' ) nie zwraca opisu AKCENTU. Wykonuje
  PP_RV_Palette_Engine::pick( 'ai' ), czyli LOSUJE nowy schemat, i zwraca jego pelna
  dyrektywe razem z kolorem sciany i kodem RAL. Blok palety wyslal juz wlasna
  dyrektywe, wiec model dostawal dwa polecenia oznaczone jako obowiazkowe.
  Losowanie wykonywalo sie osobno dla kazdego ujecia — stad trzy rozne "obowiazkowe"
  palety w jednej inspiracji i rozjazd kolorow miedzy ujeciami.
* Blok akcentu jest teraz emitowany WYLACZNIE wtedy, gdy paleta nie dostarczyla
  wlasnej dyrektywy.
* BEZPIECZNIK STRUKTURALNY: niezaleznie od zrodla zostaje PIERWSZE wystapienie
  kazdego bloku oznaczonego jako obowiazkowy (COLOUR SCHEME, ROOM TYPE,
  LOCKED ELEMENTS), kolejne sa odrzucane. Mozliwosc wyslania dwoch sprzecznych
  polecen o tej samej rzeczy jest wada budowy, nie pojedynczym bledem.

SPRZECZNE OSWIETLENIE — DWA WLASCICIELE JEDNEJ SPRAWY
* view_realism_constraints() zadalo "flat, soft, evenly diffused overcast daylight
  rather than a hard directional sunbeam" i w TYM SAMYM ZDANIU "visible surface relief
  in raking light". Swiatlo plaskie i slizgowe wykluczaja sie — blok przeczyl sam sobie.
* Przeczyl takze kontraktowi swiatla z kompozytora, ktory wymaga czytelnego gradientu
  od okna w glab pokoju. Model rozstrzygal sprzecznosc losowo, dajac raz plaskie,
  raz kierunkowe oswietlenie.
* Oswietlenie ma teraz JEDNEGO wlasciciela: light_contract(). W ograniczeniach
  realizmu zostaly wylacznie wymogi materialowe i kompozycyjne.

POLSKI PRZYMIOTNIK W ANGIELSKIM PROMPCIE
* Modul 1 brzmial "Professional interior design photography of a minimalistyczny
  home office". Pierwsze i najwazniejsze zdanie promptu nie nioslo informacji o stylu,
  bo model nie ma polskiego przymiotnika w slowniku stylow wnetrzarskich.
* Nowa metoda style_label_en() z mapa 25 stylow. Nieznany styl daje neutralne
  "contemporary" zamiast polskiego slowa. Filtr: pp_insp_style_label_en.

POWTORZENIA
* W ujeciu detalu staging_contract() i realism_anchors() opisywaly to samo zuzycie
  powierzchni innymi slowami. Kotwice realizmu pomijane dla ujecia C.

= 3.6.1 =

MIGRACJA ZAPISANYCH PROMPTOW — DOMKNIECIE 3.6.0
* Wersja 3.6.0 usunela slownictwo renderowe z KODU. To nie wystarczylo i bylo
  przeoczeniem projektowym.
* PP_Inspirations_Workflow::field() rozwiazuje wartosc w kolejnosci:
  (1) wartosc zapisana w profilu, (2) domyslna z kodu. ZAPISANY PROFIL MA PRIORYTET.
  Na instalacji, gdzie ktokolwiek kliknal "Zapisz" w Studio Workflow, stary tekst
  z fraza "A NEW photorealistic photograph..." byl utrwalony w bazie i nadal
  trafial do modelu — mimo poprawionego kodu.
* To samo dotyczylo globalnego szablonu prompta (pp_insp_prompt_template),
  instrukcji wlasnych i szablonu wizualizatora.
* NOWA KLASA PP_Insp_Vocab_Migration przepisuje ZAPISANE wartosci:
  - profile Studio Workflow (wszystkie pola tekstowe, takze zagniezdzone),
  - pp_insp_prompt_template, pp_insp_custom_instructions, pp_rv_text_analyze_step,
    pp_rv_prompt_template.
* Zasady migracji:
  - podmienia WYLACZNIE frazy z mapy, nie nadpisuje calych pol — administrator mogl
    napisac wlasna sensowna tresc wokol zakazanego slowa,
  - POMIJA pola z listami zakazow (banned_keywords, negative_prompt), bo tam te
    slowa dzialaja odwrotnie i sa potrzebne,
  - str_ireplace, bo fraza mogla byc zapisana inna wielkoscia liter,
  - porzadkuje podwojne spacje i osierocone przecinki po podmianie,
  - idempotentna i wersjonowana (stala VERSION) — kolejna zmiana slownika wymaga
    tylko podbicia stalej,
  - zapisuje kopie sprzed zmiany w opcji pp_insp_vocab_backup.
* Mapa ma 20 pozycji uporzadkowanych od fraz najdluzszych do najkrotszych — inaczej
  podmiana krotszej rozbilaby dluzsza.
* Przycisk "Wyczysc zapisane prompty ze slownictwa renderowego" w Studio Workflow
  pozwala uruchomic migracje ponownie po recznej edycji pol.

= 3.6.0 =

PRZEBUDOWA PROMPTOWANIA NA UKLAD 5 MODULOW
Zastosowano wzorzec dostarczony przez klienta. Zmiana dotyczy TORU POJEDYNCZEGO,
TORU MASOWEGO (batch vision) i REGENERACJI — czyli wszystkich sciezek, ktore skladaja
prompt obrazu.

WLASCIWY PROBLEM: REJESTR, NIE TYLKO SLOWNICTWO
* Kompozytor emitowal 24 bloki imperatywnych dyrektyw pisanych wersalikami:
  "LIGHT CONTRACT (physics, not mood - obey literally): there MUST be a visible
  luminance gradient...", "HARD FAIL CONDITIONS", "SURFACE PHYSICS (render these
  three effects explicitly)". Model generatywny nie WYKONUJE polecenia — on rysuje
  to, co opisane. Zdanie "sciana przy oknie jest jasna, dalszy naroznik wchodzi
  w cien" daje ten sam efekt fizyczny i miesci sie w rejestrze podpisu pod zdjeciem.
* Piec kontraktow (swiatlo, kompozycja, mikro-detal, aranzacja, kotwice realizmu)
  przepisano z formy rozkazujacej na OPIS.

KOLEJNOSC MODULOW
* M1 Rodzaj fotografii i styl — otwarcie promptu, "Professional interior design
  photography of a ...". Wczesniej typ pomieszczenia i styl stały w SRODKU promptu.
* M2 Podloga i sciany — opis scen z modelu jezykowego, tozsamosc produktow,
  wytyczne typu powierzchni, mikro-relief.
* M3 Wyposazenie i kolory — aranzacja, akcent, kotwice realizmu.
* M4 Swiatlo i zaslony — nastroj oraz opis kierunku, twardosci i spadku swiatla.
* M5 Techniczne zwienczenie — aparat, ogniskowa, przyslona, ostrosc, styl wydawniczy.
  KAMERA PRZENIESIONA Z POZYCJI 3 NA KONIEC. Wczesniej parametry optyki stały przed
  opisem wnetrza; wzorzec stawia je na samym koncu, jak w podpisie pod zdjeciem.
* Ograniczenia operacyjne (zakazy, blokady, wytyczne administratora) trafiaja do
  osobnego kubelka i sa doklejane ZA opisem i technika, zamiast byc przeplecione
  z opisem wnetrza.

PURGE SLOWNIKA RENDEROWEGO — 7 PLIKOW
Usunieto z promptow WYSYLANYCH DO MODELU frazy z listy zakazanej klienta.
* class-pp-inspirations-prompt-composer.php — zwienczenie zawieralo TRZY zakazane
  frazy w jednym zdaniu: "photorealistic", "ultra-detailed" i "light transport".
  Zastapione jezykiem sprzetu i publikacji.
* class-pp-inspirations-vision.php — ujecie B w torze zapasowym, makieta pomieszczenia
  w torze hybrydowym oraz "Strictly photorealistic".
* class-pp-inspirations-workflow.php — dyrektywa kroku B.
* class-pp-inspirations-preset.php — dyrektywy krokow B dla presetow.
* class-pp-inspirations-admin.php — DOMYSLNY globalny szablon prompta, ktory brzmial
  "Generate a photorealistic, ultra-detailed interior design rendering". Ten szablon
  jest widoczny w panelu i uczyl administratora blednego wzorca. Przepisany na uklad
  5 modulow. Poprawiono tez payload testu klucza API.

WEWNETRZNA SPRZECZNOSC, KTORA TO ZAMYKA
* vision.php od poczatku instruowal model jezykowy: "not with empty filler words such
  as 'hyperrealistic', '8k', 'ultra-detailed'". PHP nastepnie sam doklejal te slowa do
  gotowego promptu obrazu. Wytyczna i implementacja mowily co innego.

Frazy z listy zakazanej pozostaja WYLACZNIE w listach negatywnych ("Avoid: ...",
"no CGI or 3D-render look") oraz w komentarzach objasniajacych zmiane.

= 3.5.3 =

DOMKNIECIE WARSTWY ZGODY I PRAW OSOBY
* PP_Insp_Consent — jedno zrodlo prawdy dla tresci klauzuli, jej wersji, pobrania
  zgody, wycofania i obslugi praw z RODO. Rozproszenie tego po formularzach bylo
  przyczyna stanu, w ktorym zgody nie zbierano NIGDZIE.
* Checkbox zgody w formularzu rejestracji wizualizatora — NIEZAZNACZONY, z pelna
  trescia klauzuli i jawna informacja, ze konto dziala bez niej. Zgoda jest odrebna
  od umowy (RODO art. 7 ust. 4), wiec rejestracja konczy sie sukcesem niezaleznie
  od zaznaczenia.
* Tresc klauzuli w JEDNYM miejscu i przekazywana do JS przez i18n. Kopia w skrypcie
  oznaczalaby dwie wersje tekstu i brak mozliwosci wykazania, na ktora klient sie zgodzil.
* record_consent() zapisuje dowod: wersje, skrot SHA-256 tresci, metode i moment
  (RODO art. 7 ust. 1 — obowiazek wykazania). Zmiana brzmienia klauzuli i podbicie
  wersji uniewaznia stare zgody.
* Wypis JEDNYM KLIKNIECIEM z maila, bez logowania (art. 7 ust. 3 — wycofanie musi
  byc tak latwe jak udzielenie, a udzielenie nie wymagalo hasla). Token HMAC,
  porownanie przez hash_equals.
* Zakladka "Prywatnosc" w panelu klienta: stan zgody, liczba zapisanych zdarzen,
  udzielenie, wycofanie oraz wycofanie z usunieciem danych.
* Integracja z WBUDOWANYMI narzedziami RODO WordPressa: eksporter danych (art. 15
  i 20) oraz mechanizm usuwania (art. 17). Administrator obsluguje wniosek jednym
  klikniecem, bez zagladania do bazy.

AKCJA KONTAKTOWA — WIADOMOSC ODZYSKUJACA
* Pierwsza i jedyna akcja, ktora realnie wysyla wiadomosc. is_contact = true, wiec
  podlega globalnemu limitowi czestotliwosci i grupie kontrolnej.
* llm_exposed = false. Wysylka handlowa ma skutki prawne, wiec decyzje podejmuje
  czlowiek; model moze przygotowac tresc, ale nie moze uruchomic wysylki.
* PONOWNA weryfikacja zgody w momencie wysylki — miedzy kwalifikacja a wykonaniem
  zadania klient mogl kliknac wypis. Zgoda sprawdzona wczesniej nie jest zgoda
  obowiazujaca teraz.
* Tresc nie namawia na rabat, tylko na zobaczenie produktu we WLASNYM wnetrzu.
  Rabat obniza marze na kazdej sztuce i uczy czekac na kolejny; wizualizator nic
  nie kosztuje na sztuce i jest najsilniejszym predyktorem zakupu w tym sklepie.
* Naglowki List-Unsubscribe i List-Unsubscribe-Post: warunek przyjmowania masowej
  poczty przez najwiekszych operatorow od 2024 r.
* Pomiar odczytu (przezroczysty GIF 1x1 generowany w PHP, bez plikow i bez uslug
  zewnetrznych) oraz kliniec (przekierowanie). Oba podpisane HMAC, wiec nie da sie
  zafalszowac statystyk z zewnatrz. Odczyt liczony raz na dobe — operatorzy
  cache'uja obrazy i bez tego wskaznik bylby zawyzony.
* Przekierowanie klikniec przez wp_safe_redirect: bez tego parametr docelowy
  zamienilby sklep w otwarte przekierowanie, wygodne do phishingu pod zaufana domena.

NAPRAWY BEZPIECZENSTWA I ZGODNOSCI (3.5.2, ujete tutaj dla kompletnosci)
* has_marketing_consent() sprawdzalo BRAK sprzeciwu, czyli traktowalo milczenie jako
  zgode. RODO art. 4 pkt 11 wymaga wyraznego dzialania potwierdzajacego, a motyw 32
  wyklucza milczenie i domyslnie zaznaczone pola. Teraz wymagana jest zapisana zgoda.
* Sprzeciw wobec marketingu bezposredniego (art. 21 ust. 2) przewaza nad wcześniej
  udzielona zgoda.
* ajax_send_lead byl nieuwierzytelnionym przekaznikiem poczty: nopriv + dowolny adres
  w parametrze + wp_mail bez limitu. Kazdy mogl zasypywac cudza skrzynke z podpisem
  SPF/DKIM domeny sklepu, co psuje dostarczalnosc WSZYSTKICH maili, w tym potwierdzen
  zamowien. Trzy limity: na adres docelowy, na IP, globalny dzienny.
* ajax_send_lead nie zapisywal niczego. Teraz zapisuje ZDARZENIE w rejestrze, ale
  swiadomie NIE zapisuje adresu: podanie adresu w tym formularzu jest zadaniem
  wyslania jednej wiadomosci, a nie zgoda na marketing.

SPROSTOWANIE
* Wczesniejsza dokumentacja podawala podstawe "RODO art. 6 ust. 1 lit. a w zw. z art. 22".
  Art. 22 dotyczy zautomatyzowanych decyzji o skutkach prawnych albo podobnie istotnych
  i wyslanie maila tego progu nie osiaga. Wlasciwe zakotwiczenia: art. 6 ust. 1 lit. a
  dla profilowania, art. 21 ust. 2 dla prawa sprzeciwu, PKE dla ciasteczka i wysylki.

= 3.4.9 =

UJECIE B — WLASCIWA PRZYCZYNA POWIELANIA KADRU
* Ujecie B dostaje obraz ujecia A jako referencje tozsamosci pokoju ORAZ instrukcje
  WZGLEDNA I NEGATYWNA: "nie kopiuj kadru", "sfotografuj z innej, najlepiej przeciwnej
  pozycji". Przy silnym sygnale obrazowym zakaz przegrywa z tym, co model widzi.
  Efekt: identyczne ustawienie kamery, zmieniaja sie tylko rekwizyty (dywan, roslina).
* Rozwiazaniem nie jest mocniejszy zakaz, tylko ZADANIE POZYTYWNE. Nowa metoda
  view_b_vantage() przydziela bezwzgledny, geometryczny punkt widzenia, np. "stan tam,
  gdzie byla lewa sciana i obroc sie o 90 stopni — ta sciana jest teraz sciana, wzdluz
  ktorej patrzysz". Instrukcja geometryczna jest sprawdzalna, "inna pozycja" nie jest.
* Cztery warianty wybierane deterministycznie z ID wpisu: ta sama inspiracja
  regenerowana daje ten sam kadr B, rozne inspiracje roznia sie miedzy soba.
* Jawne warunki niepowodzenia: zoom, kadrowanie, niewielkie przesuniecie w bok albo
  ten sam kadr wyrenderowany ponownie sa bledem. W kadrze MUSI pojawic sie co najmniej
  jedna sciana, naroznik lub mebel nieobecny w referencji.
* Dyrektywa kamery B zmieniona na DWUPUNKTOWA PERSPEKTYWE NAROZNA: obiektyw celuje
  w pionowy naroznik, dwie sciany uciekaja po skosie do lewej i prawej krawedzi kadru,
  zadna sciana nie stoi rownolegle do matrycy. To projekcja strukturalnie inna niz
  ujecie ustanawiajace i to ona daje glebie.
* Instrukcja trybu reframe uzupelniona o ten sam warunek niepowodzenia.

FOTOREALIZM — TRZY BRAKUJACE ZJAWISKA FIZYCZNE
Wskazania klienta byly trafne. To zjawiska fizyczne, nie style, wiec trzeba je zamowic;
przymiotnik "fotorealistyczny" ich nie wywoluje. Nowy micro_detail_contract():
* AMBIENT OCCLUSION — miekkie przyciemnienie POGLEBIAJACE SIE stopniowo w kazdej
  szczelinie i styku: nogi mebli przy podlodze, szew listwy z podloga, szpary szuflad,
  narozniki polek, spody nawisow. Nie kreska, lecz gradient szerokosci kilku centymetrow.
* MIKRO-RELIEF POWIERZCHNI — matowa farba pokazuje fakture walka i lapie polysk pod
  katem slizgowym; drewno ma slojenie z realna zmiennoscia tonalna i wyczuwalna struktura
  porow reagujaca na swiatlo boczne; tkaniny pokazuja splot, metal kierunek szczotkowania.
* GLOBAL ILLUMINATION I PRZENIKANIE BARW — swiatlo sie odbija: podloga rzuca wlasny kolor
  na spody mebli i dolne partie scian, a nasycone powierzchnie barwia sasiadow. Cienie
  nigdy nie sa neutralnie szare — niosa kolor tego, co w nie odbija swiatlo.
* W ujeciu detalu te trzy efekty SA tematem zdjecia.
* Filtry: pp_insp_view_b_vantage, pp_insp_micro_detail_contract.

= 3.4.8 =

PROBKI KOLOROW W WIZUALIZATORZE — KAZDY KOLOR MA WLASNA BARWE
* Lista kolorow czytala wylacznie meta _pp_color_hex z fallbackiem ?: '#ffffff'.
  Te meta zapisuje TYLKO generator terminow AI. Terminy powstale inna droga hexa
  nie mialy, wiec KAZDY kafelek koloru w kreatorze klienta byl bialy i wyboru nie
  dalo sie dokonac wzrokowo.
* Nowa metoda resolve_color_hex() z lancuchem zrodel: meta HEX -> kod RAL terminu
  -> slownik ~45 polskich nazw kolorow wnetrzarskich -> deterministyczna barwa
  wyprowadzona z nazwy. Ostatni krok nie jest ladny, ale gwarantuje, ze dwa rozne
  kolory NIGDY nie wygladaja identycznie — a to jest cala funkcja probki.
* Slownik i konwersja HSL->HEX bez zaleznosci zewnetrznych.
  Filtr: pp_insp_color_name_dictionary.
* Rozwiazywanie dziala przy odczycie i nie zapisuje niczego do bazy — nie nadpisuje
  wartosci ustawionych recznie przez administratora.

PALETA NA INSPIRACJI — KOLOR GLOWNY PLUS AKCENT
* Sekcja pokazywala jedna probke. Wnetrze prawie nigdy nie jest jednobarwne:
  na zrzucie od klienta sciany byly szare, a wneka pudrowo-rozowa, ale strona
  pokazywala wylacznie szary. Akcent istnial jedynie jako etykieta tekstowa
  w "Styl i kolorystyka" — bez probki, wiec bez wartosci dla kogos, kto dobiera
  kolory do wlasnego wnetrza.
* Model zwraca teraz accent_ral i accent_name obok room_ral, z wymogiem, ze akcent
  MUSI byc innym kolorem i MUSI faktycznie wystepowac w opisie sceny.
* Zapis do _pp_accent_ral / _pp_accent_ncs / _pp_accent_name.
* Szablon pokazuje dwie probki z podpisami "Kolor glowny" i "Akcent", a naglowek
  sekcji zmienia sie na "Paleta kolorow". Gdy akcentu brak, wyglad bez zmian.
* Zapas dla starszych wpisow: akcent odczytywany z terminu taksonomii przez
  resolve_color_hex(), wiec istniejace inspiracje tez pokaza druga probke.
* Akcent identyczny z kolorem scian nie niesie informacji i nie jest pokazywany.

= 3.4.7 =

PANEL BOCZNY — PELNA WYSOKOSC, KONIEC KOMBINOWANIA
* Bledne bylo samo zalozenie, ze panel MUSI zmiescic sie w jednym ekranie. Nie musi.
  Kolumna produktowa ma prawo byc wyzsza niz okno; uzytkownik przewija strone.
  Ograniczanie jej wysokosci nie dawalo nic poza chowaniem przycisku zakupu.
* Usuniete z panelu: max-height, overflow, position:sticky, display:flex.
  Zostala naturalna wysokosc tresci.

ZNALEZIONE DRUGIE ZRODLO PRAWDY (wlasciwa przyczyna serii bledow)
* Wysokosc panelu byla ustawiana W DWOCH ARKUSZACH naraz: public.css mial wlasny
  blok @media (min-width:992px) z max-height:calc(100vh - 48px) i overflow-y:auto,
  a pp-redesign.css ustawial to samo. Kazda naprawa w jednym z nich dawala
  nieprzewidywalny wynik, bo drugi nadal walczyl o te sama wlasciwosc.
* public.css nie steruje juz wysokoscia ani pozycjonowaniem panelu. Jedynym
  miejscem, w ktorym sie to ustawia, jest pp-redesign.css.

* public.js: animowanie scrollTop panelu bylo operacja bez efektu na panelu bez
  wlasnego przewijania. Podswietlona karta produktu jest teraz doprowadzana do widoku
  przewinieciem okna, i tylko wtedy, gdy realnie jest poza ekranem — inaczej samo
  najechanie na hotspot szarpaloby strona.

= 3.4.6 =

PANEL BOCZNY — WLASCIWA PRZYCZYNA, NIE KOLEJNY OBJAW
* Panel psul sie trzy razy z rzedu. Zrodlem byl display:flex + max-height z 3.4.1.
  W kolumnie flex z ograniczona wysokoscia algorytm przydziela elementom wysokosc
  MNIEJSZA niz ich tresc, a dalej decyduje overflow:
    hidden  -> tresc UCIETA          (3.4.1: przyciety akordeon)
    auto    -> wlasny pasek          (3.4.4: dwa scrollbary)
    visible -> tresc WYLEWA SIE      (3.4.5: karty maluja sie NA bloku zakupu)
  Zaden z trzech wariantow nie jest dobry, bo zly byl sam pomysl kurczenia elementow
  ponizej ich tresci.
* Panel nie uzywa juz display:flex. Zwykly przeplyw blokowy, naturalne wysokosci,
  jeden pasek przewijania na calosci. Nic sie nie kurczy, nie wylewa i nie nachodzi.
* Zweryfikowane wizualnie na czterech kartach produktow.

FOTOREALIZM — WERYFIKACJA API GEMINI
* Sprawdzono dokumentacje ImageConfig. USTALENIE: Gemini NIE MA pola negativePrompt.
  Dostepne sa wylacznie aspectRatio, imageSize, personGeneration, prominentPeople
  (outputMimeType i outputCompressionQuality sa oznaczone jako niedostepne w Gemini API).
  Ograniczenia negatywne musza byc czescia tekstu promptu i tak dziala nasz
  negative_contract() — architektura z osobnym polem "negative" dziala na innych
  silnikach, nie na Gemini.
* Lista proporcji uzupelniona zgodnie z dokumentacja: dodano 21:9.
* seed istnieje w GenerationConfig, ale dokumentacja opisuje go jako "mostly
  deterministic", nie gwarantowany.

NOWE KONTRAKTY W KOMPOZYTORZE
* LOCKED ELEMENTS (locked_contract). Trzy ujecia powstaja z trzech osobnych wywolan.
  Wspolny seed i opis sceny pomagaly, ale nic nie mowilo modelowi WPROST, ktore
  elementy sa tozsamoscia wnetrza. Model potrafil przemalowac sciane albo wymienic
  lampe miedzy ujeciami. Model jezykowy zwraca teraz liste 4-8 zweryfikowalnych
  elementow, zapisywana w _pp_locked_elements i wysylana do KAZDEGO ujecia jako
  twarda blokada. Pomysl z architektury Scene DNA klienta.
* STAGING (staging_contract). Cel "efekt zamieszkalego wnetrza" wymaga zamowienia
  konkretow — modele domyslnie generuja przestrzen wysprzatana do zera. Wymagamy
  sladow codziennosci: zmiety koc, ksiazka polozona grzbietem do gory, kubek w uzyciu,
  krzeslo wysuniete z linii. Z jawna granica: slad zycia, nie balagan, i produkt
  zawsze czytelny.
* OPTYKA. Ujecie A: 24mm tilt-shift f/8 deep focus, kamera 1,5 m. Ujecie B: 35mm f/4,
  kamera 1,35 m. Dotad przyslona byla podana tylko dla detalu.
* OGON JAKOSCIOWY rozszerzony o physically accurate light transport i odniesienie
  do publikacji w magazynie wnetrzarskim.

= 3.4.5 =

NAPRAWA REGRESJI Z 3.4.4 — KARTA POJEDYNCZEJ INSPIRACJI
* Blok "Kup caly komplet" byl przyklejony do dolu panelu (position:sticky; bottom:0).
  Sticky dziala dokladnie tak: element PRZYKRYWA tresc, nad ktora wisi. Karta produktu
  wjezdzala pod ciemny blok i nazwa produktu znikala w polowie.
* Przyklejenie mialo zapewnic stala widocznosc przycisku, ale kosztem zasloniecia
  produktu, ktory ten przycisk sprzedaje. Blok wraca do normalnego przeplywu.
* Na mobile bez zmian — tam osobna regula robi z niego przyklejony pasek dolny.

FOTOREALIZM GENERACJI — TRZY NOWE KONTRAKTY W KOMPOZYTORZE PROMPTU
Analiza zdjec referencyjnych wykazala trzy roznice systemowe wzgledem naszych generacji.

* KONTRAKT SWIETLNY (light_contract). Dotad szla sama etykieta nastroju
  ("soft natural daylight"). Model traktuje ja jako NASTROJ, nie fizyke, i domyka scene
  rownomiernym swiatlem otoczenia — stad plaskie, bezkierunkowe kadry. Teraz swiatlo
  opisujemy jak na planie: JEDNO dominujace zrodlo, podana strona kadru, katowe padanie,
  twardosc cienia zalezna od schematu, wymagany widoczny gradient jasnosci miedzy sciana
  blizsza i dalsza od okna, zakaz niewidzialnego swiatla wypelniajacego, obowiazkowy
  cien kontaktowy przy podlodze. Filtr: pp_insp_light_contract.

* KONTRAKT KOMPOZYCYJNY (composition_contract). Zdjecia referencyjne nigdy nie sa
  symetryczne i zawsze maja element przeciety krawedzia kadru na pierwszym planie.
  Nasze "shot from a room corner" dawalo symetryczny klin dwoch scian. Teraz: masa
  wizualna poza srodkiem na linii trzech podzialow, jeden obiekt przyciety krawedzia
  kadru blizej obiektywu, piony pionowo, pokoj ma sie czytac jako fragment prawdziwego
  domu kontynuujacy sie poza kadrem. Filtr: pp_insp_composition_contract.

* KOTWICE REALIZMU (realism_anchors). Najsilniejszym sygnalem "to render" nie jest
  swiatlo ani geometria, tylko BRAK zwyklych elementow. Wymagamy teraz co najmniej
  trzech: gniazdko lub wlacznik, widoczny kabel lampy, szyna karnisza, grzejnik,
  szczelina dylatacyjna i naroznik listwy, slady uzytkowania. W ujeciu detalu:
  mikroryski, zmienna polyskliwosc, kurz w spoinie.

* LISTA NEGATYWNA (negative_contract). Nazwane sygnaly renderu do unikniecia: plaskie
  swiatlo bezkierunkowe, obiekty bez cienia, symetria na wprost, pusty showroom,
  woskowe powierzchnie, kafelkujaca sie tekstura podlogi, sciany bez gniazdek,
  zdeformowane nogi mebli.

* OPIS SCENY OD MODELU JEZYKOWEGO dostal trzy twarde wymogi, zeby nie walczyl
  z fizyka skladana w PHP: jawne wskazanie sciany z oknem (lewa/prawa), uklad
  asymetryczny, oraz min. trzy zwykle detale funkcjonalne.

= 3.4.4 =

NAPRAWA REGRESJI Z 3.4.1 — KALKULATOR NA POJEDYNCZEJ INSPIRACJI
* Panel boczny mial przypiete z nazwy tylko TRZY dzieci jako niescisliwe. Panel ma ich
  wiecej — m.in. akordeon "Oblicz ile potrzebujesz". Nieprzypiete dzieci dostawaly
  domyslne flex-shrink:1 i po osiagnieciu max-height panelu KURCZYLY SIE, ucinajac
  wlasna tresc w polowie wysokosci liter.
* Regula odwrocona: niescisliwe jest KAZDE dziecko, elastyczna jest wylacznie lista
  produktow. Nowy blok w panelu nie zepsuje juz ukladu.
* Panel przewija sie jako calosc (overflow-y:auto zamiast hidden), wiec zadna tresc
  nie moze zostac ucieta. Przycisk zakupu pozostaje widoczny przez position:sticky.
* Usunieto zagniezdzony pasek przewijania listy produktow — byly dwa scrollbary naraz.

REKOMENDACJE MONTAZOWE — PRZEPISANA LOGIKA
* Funkcja get_complementary_products() wyszukiwala produkty W TEJ SAMEJ KATEGORII co
  produkt glowny. To zwraca ZAMIENNIKI, nie uzupelnienia — do listwy przypodlogowej
  proponowala druga listwe przypodlogowa i maskownice karnisza.
* Nowa kolejnosc zrodel: (1) cross-selle ustawione recznie przy produkcie w WooCommerce,
  (2) mapa slotow montazowych z PP_RV_Product_Classifier, (3) akcesoria rozpoznane po
  nazwie. Brak trafienia = brak sekcji. Zadna podpowiedz jest lepsza niz bledna.
* Twarda regula: produkt nigdy nie uzupelnia sam siebie (ten sam slot montazowy).
* Nowe bramki: tylko produkty dostepne i mozliwe do kupienia; odrzucenie pozycji
  drozszej niz 1,5x cena produktu glownego (na zrzucie "akcesorium" za 243,61 zl przy
  produkcie za 57,62 zl). Prog sterowany filtrem pp_insp_cross_sell_max_price_ratio.
* Podbita wersja klucza cache — stare, bledne wyniki nie beda serwowane przez 12 h.
* NOWE USTAWIENIE: "Rekomendacje montazowe w oknie zakupu" — pelny wylacznik sekcji.

OKNO KONFIGURACJI ZAKUPU — WARSTWA PREMIUM
* Pozycje sa DOMYSLNIE NIEZAZNACZONE. Wczesniej kazda przychodzila z atrybutem checked,
  wiec kwota rosla bez decyzji kupujacego. Art. 10 ustawy o prawach konsumenta wymaga
  WYRAZNEJ zgody na kazda dodatkowa platnosc; zgoda domniemana z domyslnie zaznaczonego
  pola jest nieskuteczna i uprawnia klienta do zwrotu doplaty.
* Nasluch zmiany delegowany na stabilny wezel okna zamiast na swiezo tworzony kontener.
* Nowa linia "Wybrane dodatki: +X zl" — wczesniej klient widzial tylko zmiane kwoty
  lacznej i nie mial jak sprawdzic, co ja podnioslo.
* Checkboxy w kolorze marki (accent-color) zamiast systemowego blekitu. Swiadomie
  zostawiono NATYWNA kontrolke — atrapa z pseudo-elementem to najczestsza przyczyna
  "nie da sie odznaczyc".
* Stan wybrany czytelny bez patrzenia na kwadracik: obramowanie i tlo w kolorze marki.
* Plakietka "opcjonalne", podpowiedz o braku automatycznego doliczania, cele dotykowe
  56 px, widoczny focus, obsluga prefers-reduced-motion.
* Maska przewijania w tresci okna — przycieta zawartosc nie wyglada juz na blad renderowania.

= 3.4.3 =

PRODUKTY-CIENIE — ROZPOZNAWALNOSC W PANELU
* Wyjasnienie: cienie NIE byly niewidoczne. Mechanizm jest wylaczony domyslnie
  (pp_insp_feed_products_enabled = 0), wiec zaden cien nigdy nie powstal.
  Przelacznik: Wizualizator > Ustawienia > "Produkty-cienie inspiracji dla feedu".
* Po wlaczeniu cienie sa widoczne na standardowej liscie produktow (status publish,
  ukryta tylko widocznosc katalogowa). Brakowalo jednak sposobu, by je ROZPOZNAC.
* NOWE: kolumna "Zestaw" na liscie produktow z plakietka "Cien inspiracji" i linkiem
  do inspiracji zrodlowej.
* NOWE: filtr nad lista — "Tylko produkty-cienie" / "Ukryj produkty-cienie".
  Chroni przed masowa edycja cieni, ktora i tak zostalaby nadpisana przy synchronizacji.

EKRAN WYNIKU WIZUALIZATORA — PRZEBUDOWA UX
* Usunieto duplikaty akcji. Bylo: Pobierz x2, Udostepnij x3, Nowa wizualizacja x2.
  Teraz kazda akcja istnieje dokladnie raz. Dok na obrazie = akcje na obrazie
  (Pobierz, Udostepnij). Pasek pod spodem = akcje na projekcie.
* Jeden akcent kolorystyczny. Zielone wypelnienie zarezerwowane wylacznie dla
  przycisku zakupu; pozostale przyciski sa ciche (obrys). Wczesniej dwa przyciski
  w pasku byly zielone i konkurowaly z zakupem.
* Baner rejestracji przeniesiony POD sciezke zakupu. Wczesniej stal miedzy cena
  a przyciskiem kupna, przerywajac jedyna sciezke generujaca przychod.
* Uklad dwukolumnowy od 1024px: lepka scena z obrazem po lewej, kolumna decyzyjna
  (metraz + produkty + zakup) po prawej. Klient podejmuje decyzje patrzac na efekt.
* Jedna liczba glowna — szacowany koszt w skali naglowka; tabela pelnego wyliczenia
  zdegradowana do roli dowodu.
* Dok akcji przeniesiony z dolu-srodka obrazu w prawy gorny rog. Wczesniej zaslanial
  podloge, czyli dokladnie to, co klient przyszedl obejrzec.
* Zdjeto ciemna plyte spod przycisku zakupu na desktopie (na mobile pozostaje
  przyklejonym paskiem dolnym — dziala poprawnie).
* Cele dotykowe min. 44px (WCAG 2.5.8), obsluga prefers-reduced-motion.

= 3.4.2 =

**Oznaczanie produktow (piny)**
* Naprawiona regresja z 3.4.0: dwuprzebiegowy detektor hotspotow przestal sie uruchamiac, bo warunek `empty($audit)` wykluczal go za kazdym razem, gdy tor masowy przekazywal kontekst audytu. Cala detekcja spadla na starsza sciezke zapasowa ze sztywnymi pasami Y — stad piny w zlych miejscach.
* Detektor jest teraz JEDYNA sciezka lokalizacji: bounding boxy, flaga widocznosci per produkt, pomijanie produktow niewidocznych w danym ujeciu.
* Audyt wiernosci jedzie na barana razem z przebiegiem 1 detektora — jedno wywolanie, oba wyniki.

**Wybor pomieszczenia**
* Kategoria inspiracji to teraz DOKLADNIE JEDNA pozycja — ta, dla ktorej realnie poszla generacja. Wczesniej pisaly do niej dwa niezalezne zrodla: model jezykowy przez pole `categories` (tablica, wiec kilka naraz, czesto inne niz zamowione) oraz assign_room_and_style_terms() z append=true, ktore doklejalo wlasciwe pomieszczenie na wierzch.
* Nazwa pomieszczenia i stylu czytana z TAKSONOMII zamiast z zaszytej, szescioelementowej mapy. Dla pomieszczen spoza tej szostki — jadalnia, garderoba, pokoj dzieciecy — etykieta zostawala na wartosci „Wnetrze", wiec model nie wiedzial, co ma narysowac, i generowal najczestsze wnetrze z rozkladu, zwykle salon. To byla wlasciwa przyczyna zgloszenia „tworzy salon, a oznacza jako jadalnia".
* Nowa mapa polskich pomieszczen na angielskie odpowiedniki (room_label_en) — prompt obrazu jest po angielsku, a nazwa szla do niego po polsku.
* Twarda dyrektywa ROOM TYPE na poczatku wymagan, z jawnym zakazem podmiany typu pomieszczenia. Obowiazuje w master-prompcie i w kompozytorze.
* Audyt wiernosci sprawdza dodatkowo zgodnosc typu pomieszczenia (room_type_matches) — wylapie „zamowiono jadalnie, wyszedl salon" zanim redaktor zobaczy to w katalogu.

= 3.4.1 =

* Usuniete kolekcje i odznaka palety — linkowaly do adresow /paleta/{slug}/, ktore zwracaly 404, a funkcja nie byla uzywana.
* Naprawiony uklad prawej kolumny: przycisk „Skompletuj i kup zestaw" oraz cena sa teraz ZAWSZE widoczne. Poprzednia wersja robila z calego panelu obszar przewijany, przez co CTA ladowalo ponizej krawedzi i klient musial sie do niego doscrollowac wewnatrz kolumny. Przewija sie wylacznie lista produktow miedzy naglowkiem a podsumowaniem.
* Nowa zakladka „Ulubione inspiracje" w koncie klienta WooCommerce z licznikiem zapisanych pozycji. Mechanizm ulubionych istnial od dawna, ale bez miejsca w koncie klient nie mial jak zobaczyc swojej listy — shortcode wymagal recznego wstawienia na strone.
* Jednorazowe przeladowanie regul przepisywania po dodaniu zakladki, zeby adres nie zwracal 404 bez recznego zapisu trwalych linkow.

= 3.4.0 =

Wydanie porzadkujace. Usuniecie kodu bez odbiorcy powstalego przy szybkich wdrozeniach 3.2.0-3.3.4 oraz domkniecie interfejsow do funkcji, ktore istnialy wylacznie w kodzie.

**Usuniety martwy kod (1290 linii)**
* Klasa wyceny odchudzona do faktycznie uzywanej odpowiedzialnosci i przemianowana na PP_Inspirations_Cart_Set. Metoda render() nie byla wolana z zadnego miejsca od 3.3.2, gdy kalkulator przeniesiono do wizualizatora.
* Usunieta klasa profilu pomieszczenia (639 linii) — rejestrowala cztery endpointy AJAX, z ktorych zaden nie mial klienta.
* Usuniete osierocone style .pp-quote-* (122 linie) i handlery JS (107 linii) do elementu, ktory nie powstawal.

**Domkniete interfejsy**
* Widzet zuzycia limitow AI w panelu Autopilota — pokazuje OBA niezalezne liczniki: tor inspiracji i wizualizator klienta.
* Przelacznik feedu Shop the Look w ustawieniach; wczesniej opcja istniala tylko w kodzie.
* Dane strukturalne ImageObject + Product wypisywane na stronie inspiracji (metoda istniala od 3.3.0, nie byla wolana).
* Przycisk przywracania wpisow do kolejki fraz; wczesniej jedyna droga byla edycja bazy.
* Kategoria Google Product Category dla zestawow w feedzie.

**Poprawki**
* Audyt wiernosci dziala teraz we wszystkich torach: masowym, kreatorze i regeneracji ujecia. Wczesniej tylko masowy, przez co kolumna triazu pokazywala kreske dla pozostalych.
* Wynik zbiorczy wiernosci jest PRZELICZANY z zapisanych ujec zamiast zamrazany na najnizszej wartosci. Wczesniej poprawa slabego ujecia nie podnosila oceny wpisu.
* Cotygodniowe sprzatanie stanow partii kolejki; metoda istniala od 3.3.0 i nie byla wolana, przez co rekordy rosly bez konca.
* Sprostowana dokumentacja limitow kosztowych. Opis twierdzil, ze limit obejmuje wszystkie tory generowania — obejmuje wylacznie tor inspiracji. Wizualizator ma wlasny, wczesniejszy mechanizm PP_RV_Guard.

= 3.3.4 =

* Masowa synchronizacja produktow-cieni na Action Scheduler z paskiem postepu.
* Raport zgodnosci ceny feed - strona z codzienna autonaprawa o 4:15.
* Bramki jakosci: cien nie powstaje bez obrazu wyrozniajacego ani przy mniej niz dwoch produktach.
* Limity Merchant Center: tytul 150, opis 5000 znakow, przycinane na granicy slowa.
* noindex na produkcie-cieniu (Yoast, Rank Math i bez wtyczki SEO).
* Sprzatanie zaplanowanych zadan przy deaktywacji wtyczki.

= 3.3.3 =

* Naprawa integracji z Google for WooCommerce po migracji na Merchant API.
* Usuniety zapis identifier_exists — Google wycofalo te logike, a jej ustawianie powodowalo odrzucanie ofert.
* Atrybuty marki i MPN podpiete przez filtr woocommerce_gla_product_attribute_values; zapis do meta _wc_gla_* jest przez wtyczke Google ignorowany.
* GTIN celowo pusty dla zestawow — walidator wtyczki odrzucilby SKU w formacie PP-SET-*.
* Cena zestawu liczona z jednego zrodla; wczesniej ta sama matematyka istniala w szablonie i w klasie feedu.

= 3.3.2 =

* Kalkulator materialu przeniesiony ze strony inspiracji katalogowej do wizualizatora pomieszczenia, gdzie wymiary pochodza z realnego zdjecia klienta.
* Panel metrazu w wizualizatorze pokazuje pelny komplet produktow wedlug miejsca montazu; wczesniej wylacznie pierwszy produkt.
* Dodawanie calego kompletu do koszyka z wizualizatora.

= 3.3.1 =

* Naprawa przyklejenia prawej kolumny na stronie inspiracji. Regula @media (max-height:900px) wylaczala sticky na praktycznie kazdym laptopie.
* Panel przewija sie wewnetrznie, gdy jest wyzszy niz okno.
* Style dopasowane do tokenow motywu.

= 3.3.0 =

* Kolejka asynchroniczna generowania na Action Scheduler (defekt ID_DEF_42).
* Atomowa blokada zadania na add_option (defekt ID_DEF_43).
* Limity kosztowe i kontrola uprawnien w torze inspiracji (defekty ID_DEF_54 i ID_DEF_56).
* Klucz API przeniesiony do naglowka w pozostalych 5 miejscach pominietych w 3.1.0 (defekt ID_DEF_14).
* Bramka logowania ladunkow — surowe odpowiedzi modelu tylko przy WP_DEBUG (defekt ID_DEF_22).
* Audyt wiernosci renderu doklejony do istniejacego wywolania detekcji hotspotow.
* Kolumna wiernosci i filtr triazu w liscie wpisow.
* Wersjonowanie receptury Studio Workflow przy kazdej generacji.
* Klasyfikacja powierzchni scalona z siedmiu kopii do jednej.

= 3.2.0 =

* Tor hybrydowy generowania: prompt obrazu skladany deterministycznie w PHP, model jezykowy odpowiada wylacznie za opis sceny i tresci SEO.
* Wytyczne powierzchni zalezne od kadru (szeroki, sredni, makro); warianty istnialy w kodzie i nigdy nie trafialy do modelu.
* Ujecie C jako realne makro dla podlog i profili sciennych zamiast kadru z ujecia A.
* Rozdzielczosc ustawiana osobno dla kazdego ujecia.
* Kolejka generowania inspiracji z listy fraz; meta _pp_insp_seed_args nie byla wczesniej odczytywana przez zaden plik.
* Fraza docelowa dziala w torze masowym.

= 3.1.0 =

Wydanie naprawcze po audycie technicznym. 70 wykrytych defektów, w tym 10 blokerów.

**Bezpieczeństwo**
* Odcięcie nieuwierzytelnionego dostępu do płatnego generowania AI (bloker).
* Limity per-IP oraz globalny dzienny cost-cap wywołań AI. SPROSTOWANIE (3.4.0): mechanizm ten (PP_RV_Guard) chroni wyłącznie wizualizator pomieszczenia. Tor inspiracji otrzymał własne limity dopiero w 3.3.0.
* Klucz API przeniesiony z query stringu URL do nagłówka żądania. SPROSTOWANIE (3.4.0): poprawka objęła wówczas class-pp-inspirations-vision.php, ale pominęła 5 miejsc w class-pp-room-visualizer-room-analyzer.php oraz class-univibe-ai-gateway.php. Zostały one naprawione dopiero w 3.3.0.
* Usunięcie podatności XSS w podglądzie galerii.
* Usunięcie z pakietu obcego pliku konfiguracyjnego czytelnego publicznie.
* Naprawa IDOR: przejmowanie zadań gościa oraz dostęp do koszyka cudzej wizualizacji.
* Usunięcie migracji schematu wykonywanej w ścieżce żądania użytkownika.
* Blokada listowania i bezpośredniego dostępu do katalogu ze zdjęciami klientów.

**Prywatność i RODO**
* Zgoda per wizualizacja zamiast globalnej preferencji użytkownika.
* Wycofanie zgody jednym kliknięciem, ze skutkiem natychmiastowym.
* Dziennik audytowy zgód (wersja klauzuli, hash treści, hash IP).
* Rozprzęgnięcie zgody od rabatu.
* Eksport i usuwanie danych przez narzędzia prywatności WordPressa.
* Usuwanie metadanych EXIF/GPS ze zdjęć wgrywanych przez klientów.

**Moderacja**
* Nowy panel moderacji treści klientów z akcjami masowymi.
* Maszyna stanów: szkic → oczekuje → zatwierdzone / odrzucone / wycofane.
* Wyłączenie automatycznej publikacji kolejnych wizualizacji.

**Generowanie i oznaczanie**
* Nowy builder promptów z fizyką materiału per rodzaj produktu.
* Podłoga jest wymieniana, nie nakładana na istniejącą powierzchnię.
* Usunięcie sprzecznych instrukcji o oświetleniu w jednym prompcie.
* Klasyfikator produktów oparty o kategorie WooCommerce zamiast dopasowania nazw.
* Dwuprzebiegowa detekcja hotspotów bez zaszytych pasów współrzędnych.

**Wydajność**
* Likwidacja zapytań LIKE na liście produktów — tabela relacyjna i indeksy.
* Poprawny licznik wizualizacji w zakładce produktu.
* Usunięcie podwójnego renderowania galerii.
* Ograniczenie budżetu czasowego blokujących ponowień generowania.

**Generowanie wizualizacji i treści (3.1.0a)**
* Naprawiono montaż maskownicy karnisza: profil mocowany do sufitu, nie do sciany; swiatlo LED omiata zaslone w dol.
* Doprecyzowano montaz listew przysufitowych, rozet i listew przypodlogowych: kazdy profil ma wskazana powierzchnie mocowania.
* Naprawiono formatowanie opisow: konwersja pozostalosci Markdown na HTML, wymuszenie dokladnie jednego naglowka H2.
* Rozszerzono okno kontroli unikalnosci tytulow z 30 do 200 ostatnich wpisow; dodano deduplikacje H1.
* Silnik palet: 15 schematow kolorystycznych z rotacja i rownowazeniem jasnosci zamiast stalych bialych scian.
* Nowosc: masowe tworzenie inspiracji z listy fraz. System rozpoznaje pomieszczenie, styl, kolor bazowy,
  akcent, wzor ukladania i typ produktu bezposrednio z tekstu frazy.

**Warstwa fraz SEO (3.1.0d)**
* Nowy generator fraz long-tail: podajesz branze lub kategorie, dostajesz liste fraz
  posortowana malejaco wg szacowanego miesiecznego wolumenu w Polsce, z podzialem na
  pomieszczenie, intencje i sugerowany typ tresci.
* Fraza jest teraz CELEM SEO wpisu, a nie tylko zrodlem parametrow wizualnych.
  Tytul, H1, pierwsze 100 slow tresci i meta description sa budowane wokol niej.
* Naprawiono martwy zapis: _pp_insp_seed_phrase byl zapisywany i nigdy nieczytany.
* Nierozpoznanie koloru, stylu lub pomieszczenia we frazie nie blokuje juz generowania.
* Wolumeny sa jawnie oznaczone jako szacunek modelu, nie dane z Ahrefs czy Search Console.

**Poprawki oznaczania produktow (3.1.0c)**
* Usunieto zdublowany dymek produktu: pozostal jeden mechanizm (.pp-hotspot-tooltip).
* Dymek renderowany w kontenerze obrazu zamiast wewnatrz kropki — nie moze juz zostac przyciety.
* Wszystkie automatyczne sciezki generowania (ujecie A, ujecie B, kandydaci, regeneracja)
  przepiete na nowy detektor hotspotow. Wczesniej korzystal z niego tylko reczny przycisk
  "Ponowna detekcja AI", przez co poprawki trafnosci nie dzialaly przy normalnym generowaniu.

**Wizualizator ze zdjecia klienta (3.1.0b)**
* Kolejka asynchroniczna (Action Scheduler) zamiast generowania w cyklu zadania HTTP; zero sleep() w request.
* Zmiana produktu bez ponownego wgrywania zdjecia — jedna analiza, wiele wariantow.
* Porownanie do 3 wariantow tego samego pomieszczenia obok siebie.
* Szacunek metrazu ze zdjecia z przelozeniem na liczbe paczek i koszt, zawsze jako przedzial z zastrzezeniem.
* Limit produktow 2 -> 4 z kontrola powierzchni (jeden produkt na podloge/sciane/sufit).
* Prowadzona naprawa odrzuconego zdjecia: konkretna wskazowka dla kazdego z 8 kodow bledu.
* Pomiar lejka wizualizatora: viz_open, viz_upload, viz_rejected, viz_generated, viz_variant, viz_add_to_cart.
* Widoki B i C pokazuja TO SAMO pomieszczenie z innego miejsca, z zablokowana tozsamoscia sceny i paleta.
* Kadry w jakosci testu H2c: widok B z kamera 0,9 m, podloga na dolnych 2/3 kadru, piony rownolegle.
* Detektor hotspotow moze pominac produkt niewidoczny w danym ujeciu zamiast wymuszac pozycje.
* Dymek produktu nie wychodzi juz poza kadr: przerzucanie w pionie, docinanie w poziomie, dokowanie na mobile.

**Struktura linków i SEO**
* Usunięto duplikat archiwum: /inspiracje-galeria/ przekierowuje 301 na /inspiracje/.
* Breadcrumbs odzwierciedlające silos wraz z BreadcrumbList JSON-LD.
* Sitemapa dzielona per taksonomia przy 100+ wpisach.
* Linkowanie wewnętrzne z kontrolą proporcji anchorów i limitem linków międzysilosowych.
* Kontrola jakości treści: 60 zakazanych fraz, 34 kalki, walidacja gramatyki nagłówków.
* Generator treści z blokiem odpowiedzi GEO, sygnałami E-E-A-T i kontrolą rytmu zdań.
* Ujednolicono limity znaków tytułu i meta (60/58/152) między promptem a kodem.

**Kompletność**
* Dodano uninstall.php, readme.txt, .htaccess oraz szablon tłumaczeń.
* Naprawiono martwą warstwę i18n (brak load_plugin_textdomain).
* Naprawiono błąd krytyczny na PHP 7.4 (str_contains w ścieżce generowania).

= 3.0.8 =
* Wersja przed audytem.