feat(guide): popupy i zwijanie narracji w przewodniku PDF#73
Conversation
Opisuje zamianę `_Capture.page` z „okna głównego" na „okno aktywne", cykl życia sterowany zamrożoną flagą `opens_popup` zamiast heurystyki, oraz letterboxing popupu w CSS zamiast przetwarzania obrazu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEZwp8YivARFrZSjrSUnD5
`guide` odrzucał każdy scenariusz z `opens_popup: true` — a więc całą klasę scenariuszy logowania, dla których przewodnik krok-po-kroku ma największy sens. Odmowa była uczciwa: pakiet nie miał żadnego cyklu życia okien, `_Capture.page` było ustawiane raz z okna głównego, a krok działający na popupie szukałby celu w złym oknie i padał kilka kroków później na niezgodności tożsamości. Zmienia się jedno pojęcie: `_Capture.page` znaczy teraz „okno aktywne", nie „okno główne". Pętla przechwytywania nie dowiaduje się o niczym — te same pięć wywołań `_screenshot(cap.page, ...)` fotografuje inną wartość. Cykl życia sterowany jest zamrożoną flagą, nie heurystyką: klik z `opens_popup` owija się w `context.expect_page()` i przełącza parę `(page, recorder)`, a `closeWindow` — dotąd komenda wyłącznie narracyjna, bo nie miała czego zamykać — dostaje własny rodzaj strony i przełącza z powrotem. Świadomie bez `render/popup_detect.py` i `popup_crop.py`: te 789 linii to maszyneria wideo, a PDF nie ma osi czasu. Popup jest mniejszy od okna głównego, więc `layout.py` centruje go na płótnie o proporcjach okna głównego — w CSS, bez przetwarzania obrazu. Adnotacje przenoszą się z `.shot` do wewnętrznego `.plate` o rozmiarze zrzutu: pinowane do `.shot` rozjechałyby się o szerokość marginesu. Sam fakt „to popup" nie jest nigdzie flagą — różnica `screenshot_size` i `canvas_size` nią jest. Zweryfikowane end-to-end na scenariuszu logowania do Onetu: 8 stron, zrzut popupu 500x670 obok stron 1376x800 okna głównego. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEZwp8YivARFrZSjrSUnD5
`--pause-on-error` dostawał parametr `page` funkcji `capture_pages`, czyli zawsze okno główne. Dopóki było jedno okno, była to jedyna możliwa odpowiedź. Odkąd `_Capture.page` znaczy „okno aktywne", błąd w popupie zostawiałby dewelopera przed oknem głównym, na którym nic złego nie widać. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEZwp8YivARFrZSjrSUnD5
Krok `say:` między akcjami opisuje to, na co czytelnik patrzy — a dostawał osobną kartkę z jednym zdaniem na środku. Teraz ląduje w panelu bocznym poprzedniej strony ze zrzutem, pod poziomą kreską. Zwijanie nie dotyczy slajdów (celowa plansza pełnoekranowa nie absorbuje ani nie jest absorbowana) ani narracji, przed którą nie ma żadnego obrazka. Pusta narracja zwija się w nic zamiast w samotną kreskę — przy okazji znikają puste kartki po krokach narracyjnych bez tekstu. `fold_narration` jest publiczne i wołane przez `run_guide`, nie przez `render_html`. Zwijanie ukryte w rendererze zostawiało wywołującemu listę sprzed zwinięcia jako jedyną miarę dokumentu — a `guide` zwraca jej długość jako liczbę stron, więc CLI ogłaszało osiem stron pięciostronicowego PDF-a. Jedna lista, liczona i drukowana, nie może się rozjechać. Na scenariuszu logowania do Onetu: 8 stron -> 5, bez utraty jednego słowa narracji. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEZwp8YivARFrZSjrSUnD5
Dodane: zwijanie narracji bez własnego kadruDrugi, niezależny problem tej samej ścieżki: krok Zwijanie nie dotyczy slajdów (celowa plansza pełnoekranowa ani nie absorbuje, ani nie jest absorbowana) i narracji, przed którą nie ma obrazka. Pusta narracja zwija się w nic zamiast w samotną kreskę — przy okazji znikają puste kartki po krokach narracyjnych bez tekstu.
Na scenariuszu logowania do Onetu: 8 stron → 5, bez utraty jednego słowa narracji. |
Problem
guidebot guideodrzucał każdy scenariusz, w którym jakikolwiek krok otwiera nowe okno:Dotyczyło to całej klasy scenariuszy logowania — a więc tych, dla których przewodnik krok-po-kroku ma największy sens.
Odmowa nie była kaprysem: pakiet nie miał żadnego cyklu życia okien.
_Capture.pageustawiano raz z okna głównego i każde z pięciu wywołań_screenshot(cap.page, …)fotografowało właśnie je. Krok działający na popupie szukałby celu w oknie głównym i padał na niezgodności tożsamości — kilka kroków za późno, w niezrozumiałym miejscu.Rozwiązanie
Zmienia się jedno pojęcie:
_Capture.pageznaczy teraz „okno aktywne", nie „okno główne". Pętla przechwytywania nie dowiaduje się o niczym — te same pięć wywołań fotografuje inną wartość.Cykl życia sterowany sidecarem, nie heurystyką.
compilejuż zamrażaopens_popup: true, więc klik z tą flagą owija się wcontext.expect_page()i przełącza parę(page, recorder);closeWindowprzełącza z powrotem. Ta komenda była dotąd wyłącznie narracyjna — nie miała czego zamykać — więc dostaje własny rodzaj strony.Świadomie bez
render/popup_detect.pyirender/popup_crop.py(789 linii): to maszyneria wideo — wykrywanie momentu z kwantem ciszy, przejścia float/slide, kadrowanie klatek. PDF nie ma osi czasu.Płótno w CSS, nie w obrazie. Popup otwiera się mniejszy niż okno główne, więc
layout.pycentruje go na płótnie o proporcjach okna głównego — bez przetwarzania obrazu i bez nowej zależności. Adnotacje przenoszą się z.shotdo wewnętrznego.plateo rozmiarze zrzutu: przypięte do.shotrozjechałyby się o szerokość marginesu. Sam fakt „to popup" nie jest nigdzie flagą — różnicascreenshot_sizeicanvas_sizenią jest, więc dwa źródła prawdy nie mogą się rozjechać.Weryfikacja
End-to-end na scenariuszu logowania do Onetu, dotąd odrzucanym:
Rozmiary zrzutów potwierdzają przełączenie okien:
step-006.pngma 500×670 (popup), pozostałe 1376×800 (okno główne).pytest -q -m "not network"(komenda zci.yml): 1591 passed, 1 skippedruff checkz bramką C901 ≤ 10: czystoreplay.py— 534Testy
Nowy
tests/unit/guide/test_capture_popup.py(7 przypadków, bez przeglądarki) pinuje kolejność, bo to ona jest sednem: krok otwierający popup jest fotografowany w oknie, w którym czytelnik klika, a dopiero kolejne — w popupie. Odwrotnie i każdy przewodnik z logowaniem pokazuje obrazek złego okna w momencie akcji.Poza tym: powrót na
closeWindow, zerowanie śladu kursora przy zmianie okna (strzałka między oknami wskazywałaby współrzędne, które na tej stronie nic nie znaczą), brak okna mimo flagi → twardyGuideErrorzplik:linia, oraz--pause-on-errorzatrzymujący się na oknie, w którym krok padł.Dawne
test_scan_raises_on_popupzostało odwrócone wtest_scan_allows_a_click_that_opens_a_popup.Spec:
docs/superpowers/specs/2026-07-25-guide-popup-support-design.md🤖 Generated with Claude Code
https://claude.ai/code/session_01PEZwp8YivARFrZSjrSUnD5