refactor(render): run_render 97 → 1 (faza 3b)#69
Merged
Conversation
`card_active` i `active_card` były jedną informacją zapisaną w dwóch miejscach: `card_active == (active_card is not None)` w każdym z ~10 miejsc zapisu, a kod jawnie to asertował. Para nie mogła wyrazić niczego poza rozjazdem, więc znika na rzecz `card: Card | None`. Zero zmian zachowania: każdy warunek `card_active` staje się `card is not None`, każda para przypisań — jednym przypisaniem.
…zeglądarką Pierwszy z trzech czasów życia, które `run_render` przeplatał. `plan.py` zbiera preflight: wczytanie scenariusza, nadpisania z CLI, walidację sidecara, ikony pulpitu i pre-syntezę narracji. Trzy domknięcia (`step_message`, `note_skip`, `persist_resolved`) stają się metodami — to były punkty CC rodzica przy zerowej zmianie zachowania. Kolejność w `_prepare_render` jest kontraktem: każde odrzucenie sidecara musi paść PRZED pre-syntezą, która potrafi spędzić minuty w providerze TTS. Dlatego banner (`plik:linia` + fragment YAML) jest osobnym, małym obiektem `_Banner`: walidacja go potrzebuje, zanim plan może istnieć. run_render: CC 97 -> 82. Zero zmian zachowania.
…ptów init
Drugi z trzech czasów życia. `stage.py` trzyma wszystko, co istnieje tylko
razem z kontekstem przeglądarki: strony, wstrzyknięte warstwy, popup, kartę
slajdu. Sześć domknięć `run_render` (`_chrome_hide`, `_chrome_show`,
`_assert_card_alive`, `_ensure_card`, `observe_page`, `mark_closed`,
`_reveal_shell`) staje się metodami albo funkcją modułową — czyli punktami CC
rodzica oddanymi przy zerowej zmianie zachowania.
Niezmiennik kolejności przestaje być komentarzem: cała rejestracja skryptów
init to jedna funkcja `_install_page_scripts`, której CIAŁEM jest kolejność,
plus sprawdzenie w czasie wykonania — czwarta warstwa dopisana bez ogłoszenia
w `_ROLE_GATED_LAYERS` (albo wsunięta po chrome.js) zatrzymuje render na
pierwszej linii zamiast po cichu zamontować drugi kursor w iframie strony.
`_Stage` świadomie NIE jest `slots=True`: `observe` trafia do
`context.on("page", ...)` jako metoda związana, a Playwright zapamiętuje swój
wrapper przypisując atrybut na `__self__`.
Szwy `Overlay` i `SlideOverlay` wędrują z konstruktorami do `stage` — 10
miejsc podmiany w `test_render.py` przecelowanych w tym samym commicie.
run_render: CC 60. _run.py: 592 linie.
Trzeci z trzech czasów życia. `clock.py` zbiera zamrożenia, umiejscowienia SFX i narracji — wszystko w KLATKACH osi nagrania, nie w sekundach. Sedno: `last_freeze_frame` zostaje POLEM, a callback `on_sfx` — METODĄ ZWIĄZANĄ. `on_sfx` odpala się o jedną ramkę wywołania niżej, w `_render_step`, i musi czytać `last_freeze_frame` W TAMTEJ CHWILI (zamrożenie z narracji tego samego kroku jest właśnie tym, przed czym ma się odsunąć). Przekazanie wartością zrywa to PO CICHU: każde sprawdzenie długości dalej przechodzi (`probe_frame_count == virtual_frames`, tolerancja muxa, kontrola przekroczenia ścieżki), bo zapadnięte umiejscowienie nie zmienia długości filmu. Metoda związana JEST tym domknięciem — czyta `self` w chwili wywołania, więc nie ma wartości do przekazania i nie ma czego zestarzeć. Dowód mutacyjny: `stamp()` bez klamry (`not_before=0`) wywala `test_hold_frame_narrations_never_overlap` oraz `test_hold_frame_narrations_inside_taken_branch_never_overlap`. run_render: CC 51. _run.py: 550 linii.
…to szew `loop.py` rozbija ciało pętli na fazy, każda z jednym argumentem `_StepCtx` (plan + stage + clock + krok), nie na osiem pozycyjnych. Niezmiennik „sonda nieobecności PRZED narracją" przestaje być komentarzem: `_probe_absence` („czy ten krok w ogóle się dzieje") jest osobną funkcją od `_narrate`/`_perform` („zrób to"), a szwem jest sama granica między nimi. Zamrożona bramka to jedyny przypadek nierozstrzygalny przed akcją — jej `waitFor` JEST akcją — więc wraca z `_render_step` jako `_OptionalAbsent`; obie nieobecności lądują w `_note_absent`. Przy okazji dwa identyczne bloki `except Exception` (redakcja, komunikat, `pause_on_error`) stają się jedną funkcją `_fail_step` — używaną dwukrotnie, więc to nie jest jednolinijkowy pomocnik na jedno wywołanie. Szew `Recorder` wędruje z konstruktorem do `loop` (5 miejsc podmiany). run_render: CC 97 -> 6. _run.py: 231 linii (było 941).
`post.py`: nagranie -> skomponowane -> wirtualne -> zmasterowane. Kompozycja popupu MUSI iść przed edycją czasu, a nazwy stadiów (`_RecordedFilm`, `_ComposedFilm`, `_VirtualFilm`) sprawiają, że czyta się to jak kolejność, a nie jak dwa sąsiadujące akapity. Repo nie ma type-checkera, więc te typy są czytelnością, nie wymuszeniem — realną ochroną zostaje test z fazy 0 (`test_popup_is_composed_before_time_editing_and_feeds_it`). Szwy `compose_popup_video` i `probe_frame_count` wędrują z wywołaniami do `post` (3 miejsca podmiany). _run.py: 84 linie i ZERO szwów — cała reszta przeniosła się do modułu, który faktycznie konstruuje albo woła daną nazwę.
`_render_step` to dyspozycja na `kind` scenariusza ORAZ na `cached.action` sidecara, w relacji wiele-do-wielu (`teach` zamraża się do dowolnej akcji, `wait` rozjeżdża się między obie na `requires_target()`). Pojedynczy rejestr na `kind` byłby strukturalnie błędny — każdy handler musiałby powtórzyć strażników zamrożonej akcji, którzy siedzą MIĘDZY dyspozycjami. Kształt: dwa krótkie łańcuchy `if` z jednolinijkowymi delegacjami i kontekstem `_Replay` jako JEDNYM argumentem, nie ośmioma pozycyjnymi. `say`, które nie potrzebuje niczego, zostaje dwoma liniami `return`, a nie pustą implementacją jednolitego protokołu. Druga dyspozycja NADAL nie ma `else` — nieznana akcja sidecara nic nie robi i nic nie zgłasza. To utajony błąd z backlogu specu; naprawa jest zmianą zachowania i należy do osobnego commita z własnym testem. Tutaj przenoszony bajt w bajt, z jawnym komentarzem w docstringu modułu. Domknięcie `mark_click_started` (z `nonlocal`) staje się `_ClickWatch` — znowu metoda związana, czytająca żywy zbiór stron w chwili kliknięcia. _render_step: CC 49 -> 2. _step.py: 537 linii.
…bajt 8 z 19 punktów tej funkcji to dwa domknięcia (`build_track`, `build_bounded`) i zostają na miejscu: jedno z nich domyka cztery zmienne i jako funkcja modułowa wymagałoby przewleczenia sześciu parametrów przez `asyncio.to_thread`. Wychodzą dwie rzeczy: strażnik przekroczenia narracji (`_assert_narration_fits`) i zbiórka z drenażem po anulowaniu (`_gather_tracks`). Ta druga to poprawny względem anulowania asyncio z ręcznym shield/drain, od którego zależy czas życia `TemporaryDirectory` — przeniesiona bez jednej zmiany, z komentarzem „nie sprzątać". _mux_tracks_for_timeline: CC 19 -> 9.
…Kind Druga dyspozycja `_render_step` nie ma `else`: nieznana akcja nic nie robi i nic nie zgłasza. To utajony błąd, starszy niż faza 3, i spec trzyma go w backlogu, bo naprawa jest ZMIANĄ ZACHOWANIA. Ten commit jej nie robi. Robi coś, co daje tę samą ochronę bez zmiany zachowania: przypina totalność dyspozycji. Dziś gałąź `else` jest nieosiągalna, bo `CachedAction.action` to `ActionKind` — `Literal` z sześcioma wartościami na modelu pydantica, więc sidecar z czymkolwiek innym leci przy wczytaniu. Realnym ryzykiem jest SIÓDMA akcja dopisana do `ActionKind`, której dyspozytor po cichu zignoruje — krok zagrałby jako pusta pauza w gotowym filmie. Strażnik czyta AST `_replay_action` i porównuje zbiory. Zweryfikowany mutacyjnie: usunięcie gałęzi `waitFor` wywala go z nazwą osieroconej akcji.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ostatnie trzy naruszenia complexity w repozytorium.
run_render_render_step_mux_tracks_for_timeline_run.py941 → 85. Po tym PR-ze: zero naruszeń C901 i zero plików kodu ponad 600 linii w całym repo.Najwyższe CC czegokolwiek nowego: 9. Każda nowa funkcja ≤ 8.
Trzy obiekty stanu o różnych czasach życia
_RenderPlan(frozen) — wszystko ustalone zanim istnieje przeglądarka ·_Stage— co jest teraz na ekranie ·_Clock— oś nagraniarun_renderto teraz dziewięć instrukcji: przygotuj → otwórz scenę → wystartuj zegar → przejdź kroki → opublikuj film.Nowe moduły:
stage.py456,loop.py506,plan.py303,post.py227,clock.py134.Niezmienniki kolejności — teraz strukturalne
Rejestracja skryptów init: cała w jednej funkcji, której ciało jest kolejnością, a warstwy podlegające role-gatingowi powstają ze słownika sprawdzanego wobec
_ROLE_GATED_LAYERSna starcie renderu. Czwarta nakładka dodana bez zadeklarowania podnosiRenderErrorw pierwszej linii renderu, zamiast zamontować duplikat kursora wewnątrz ramki — defekt widoczny wyłącznie w gotowym filmie.Kompozycja przed edycją czasu: ogon przeniesiony do
post.pyjako trzy nazwane etapy (_RecordedFilm→_ComposedFilm→_VirtualFilm), więc kolejność jest przepływem danych, a nie dwoma sąsiadującymi akapitami.Sonda nieobecności przed narracją: teraz granica funkcji — „czy ten krok w ogóle się wydarzy" jest oddzielone od „wykonaj go".
Monotoniczność
last_freeze_frame:_Clockjest jej jedynym czytelnikiem i jedynym pisarzem;on_sfxto metoda związana, więc odczytujeselfw momencie wywołania — dzisiejsza semantyka z konstrukcji.Znalezisko: jeden z testów z Fazy 0 jest pusty
test_sfx_after_a_freeze_never_lands_inside_the_holdnie dyskryminuje na tej maszynie i nigdy nie dyskryminował. Wykonawca nie zdołał go wywalić — ani przez migawkę wartości, ani przez całkowite usunięcie klamry (not_before=0). Sprawdził to samo usunięcie na stanie sprzed refaktoru (527e9f4): też zielone.Wyścig, który ten test opisuje, nie materializuje się na tym sprzęcie. Problem istniał wcześniej i nie został wywołany tą pracą — ale oznacza, że projekt oparty na metodzie związanej jest po stronie SFX chroniony rozumowaniem, nie testem. Strona narracji jest pokryta naprawdę (dowód mutacyjny (a)).
Korekty do mojej analizy
Utajony brak
elsew dyspozytorze jest nieosiągalny, nie tylko nieprzetestowany.CachedAction.actiontoActionKind—Literalz dokładnie sześcioma wartościami na modelu pydantica; sidecar nazywający cokolwiek innego jest odrzucany przy wczytaniu. Zachowanie nie zostało zmienione; zamiast tego doszedł strażnik AST przypinający kompletność dyspozycji wobecActionKind, więc siódmej akcji nie da się już dodać po cichu.Scalenie
card_active/active_carddaje 0 punktów CC, nie ~3. Liczbaif-ów się nie zmienia; znikaassert(0 CC) i klasa błędów desynchronizacji. Warto było, ale nie dla metryki._Stagenie może użyćslots=True.stage.observetrafia doBrowserContext.on("page", …)jako metoda związana, a Playwright memoizuje swój wrapper przezsetattrna__self__— instancja ze slotami rzucaAttributeErrorz wnętrza dyspozytora zdarzeń.Dowody mutacyjne
last_freeze_frameprzekazany wartościągot ['edit', 'compose']waitForz dyspozycji_run.pynie zawiera już żadnego szwu — 18 miejsc podmiany przecelowanych, atest_render_seams.pywyłapał cztery, które wykonawca przeoczył za pierwszym podejściem. Dokładnie po to powstał.Testy
1529 unit + 51 integration (po scaleniu z Fazą 3a), 1 skipped
ruff checki preview blank-lines czysto · C901: zero naruszeń w repo🤖 Generated with Claude Code
https://claude.ai/code/session_0134rSCftVxsMVL8sngceT7A