Skip to content

feat(masters): add --clear-headline/--clear-text to masters update (#786) - #787

Merged
axisrow merged 4 commits into
mainfrom
feat/648-stage-b-weighted-lists
Aug 6, 2026
Merged

feat(masters): add --clear-headline/--clear-text to masters update (#786)#787
axisrow merged 4 commits into
mainfrom
feat/648-stage-b-weighted-lists

Conversation

@axisrow

@axisrow axisrow commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the Этап B follow-up left open by #665 (masters update --headline/--text): those flags refuse an empty replacement (indistinguishable from a delete request to the post-save verification), and deleting a variant was tracked as a separate follow-up rather than folded into them. This PR adds that delete path.

Part of the umbrella issue #648 — specifically the remaining Этап B scope. Per live recon already on file (tests/fixtures/masters_wizard_edit_stage_b.html), Мастер кампаний has no per-variant weight/priority UI at all, so "списки с весами" from the original #631/#648 wording does not apply here — there is nothing for a weight flag to set. Adding brand-new variants is also out of scope: the edit page has a fixed slot count (5 headlines / 3 texts) with no "add another" control.

Changes

  • direct_cli/browser/masters.py: new _clear_repeating_value clicks a slot's own .clear button (CampaignTitles{N}.clear/CampaignTexts{N}.clear — confirmed live in the existing fixture's "BONUS FINDING", previously unused). update_master gained clear_headlines/clear_texts kwargs, wired into the mutation loop and into _verify_saved (a cleared slot is expected to read back as "", reusing the existing _verify_repeating_value_mismatches check).
  • direct_cli/commands/masters.py: new repeatable --clear-headline/--clear-text options (1-based slot number, same numbering as --headline/--text). New _parse_clear_slot_options (bounds/duplicate checks) and _reject_overlapping_slots (a slot can't be both set and cleared in the same call — rejected as UsageError before any browser session opens).
  • tests/test_masters.py: offline unit coverage for _clear_repeating_value (browser layer, fake Page/Locator) and the new CLI flags/parsers/guards (~30 new tests).
  • README.md / CHANGELOG.md updated.

Verification

Offline: full suite green (pytest, 3346+ passed). black/flake8 clean.

Live: not yet done in this PR — per project convention (feedback_live_verification_before_tests) and the current serialized queue for the shared test master/browser session (other sessions #782/#783/#776/#781 active), live verification of --clear-headline/--clear-text against a real campaign is queued separately and will follow before this is considered fully verified, mirroring how #665 itself shipped its offline PR first and was live-verified afterward.

Out of scope (confirmed, not merely deferred)

🤖 Generated with Claude Code

axisrow and others added 2 commits August 6, 2026 16:37
…786)

Closes the Этап B follow-up left open by #665: `--headline`/`--text`
refuse an empty replacement (indistinguishable from a delete to the
post-save verification), and deleting a variant was deferred rather
than folded into that flag.

- New `_clear_repeating_value` (browser layer) clicks a slot's own
  `.clear` button (`CampaignTitles{N}.clear`/`CampaignTexts{N}.clear`),
  confirmed live and documented in
  tests/fixtures/masters_wizard_edit_stage_b.html's "BONUS FINDING".
- New `--clear-headline`/`--clear-text` CLI flags (repeatable, 1-based
  slot number, mirrors --headline/--text numbering).
- A slot passed to both a set flag and its clear counterpart in the
  same call is rejected as a UsageError before any browser session
  opens. Clearing an already-empty slot is refused too.
- Verification reuses the existing _verify_repeating_value_mismatches
  re-read-and-compare check (a cleared slot is expected to read back
  as "").

Out of scope, confirmed by live recon rather than merely deferred:
adding a brand-new variant (Yandex's edit page has a fixed slot count,
no "add another" control) and per-variant weights (Мастер кампаний has
no weight/priority UI at all for these slots, unlike ordinary
text-campaign ad variants).

Part of the #648 umbrella (Этап B done here; Этап D media uploads
remains). Offline-only in this PR — live verification queued
separately per the project's serialized browser-session access.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGxHgqUmX2YeA1ignZSef1
…ble save

_clear_repeating_value clicked its per-slot .clear button and returned
immediately, with no confirmation the textarea actually emptied before
the caller's _click_save fires. This module already learned (issue #681)
that a button click on this same edit page is async and "click alone
isn't proof" — _remove_audience_tag/_add_audience_tag poll BEFORE save
and raise on a non-committing click, precisely to avoid persisting a
half-committed state. The new clear path lacked that guard, so a click
that hadn't committed could reach the irreversible save; the mismatch
would only surface afterwards via the post-save verification re-read,
by which point the save had already happened.

Add the same post-click poll-until-empty guard, with the same
_AUDIENCE_TAG_SUGGEST_TIMEOUT_MS budget, and raise BrowserSessionError
before the caller ever gets to save if the slot never empties.

Found by cycle-review (Codex adversarial review) on PR #787.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGxHgqUmX2YeA1ignZSef1
@axisrow

axisrow commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

🔍 Local review (cycle 1) — round 936a027b-ed1d-4497-9086-d87e38bf1078

Reviewed locally: built-in /review was unavailable in this environment (Skill tool refused with disable-model-invocation), so this round used the Codex companion only, with explicit user sign-off to proceed Codex-only. No GitHub bots were pinged.

Verdict Reviewer Finding Location
FIX codex Clear-slot click was not confirmed to commit to the DOM before the irreversible save fired direct_cli/browser/masters.py:7361

Resolution: fixed in 6cf8e5b — added a post-click poll-until-empty guard (same budget/pattern as the audience-tag add/remove loop, issue #681) so a non-committing clear click is caught before _click_save, not only afterwards via the post-save verification re-read. Full offline suite green (3348 passed), black/flake8 clean.

@axisrow axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ревью PR #787

Вердикт: approve. Замечаний, требующих изменений, не нашёл.

Что проверено

  • Diff direct_cli/browser/masters.py / direct_cli/commands/masters.py / tests/test_masters.py относительно origin/main на head-коммите 6cf8e5b.
  • _clear_repeating_value: bounds-check, чтение текущего значения, guard на уже пустой слот, клик по .clear, и — что особенно важно — post-click commit-check с poll-циклом через _clock.now()/page.wait_for_timeout (не raw time), то есть соблюдён контракт _raw_clock_calls/TestBrowserPackageClock для poll-циклов в browser/. Второй коммит (6cf8e5b) добавляет именно этот commit-check как фикс по cycle-review-находке — обоснованно: клик по асинхронной кнопке без проверки, что состояние действительно применилось, до необратимого _click_save — реальный риск, ровно как в прецеденте с audience-tag close button (#681).
  • CLI-граница (_parse_clear_slot_options, _reject_overlapping_slots) отклоняет: слот вне диапазона, дубликаты, пересечение --headline/--clear-headline на одном слоте — до открытия браузерной сессии (подтверждено тестом test_out_of_range_clear_slot_does_not_open_a_browser_session).
  • update_master: verify_headlines/verify_texts корректно мёржат headlines/clear_headlines в единую карту ожидаемых значений для _verify_repeating_value_mismatches — очищенный слот должен читаться как "", что уже покрывается существующей проверкой равенства.
  • Оverlap-проверка есть только на CLI-границе, не дублируется в browser-слое — задокументировано и последовательно с остальными парными опциями модуля (target_action_prices/add_target_actions/remove_target_action_goal_ids).
  • README.md/CHANGELOG.md обновлены, out-of-scope (веса, добавление новых вариантов) явно задокументированы со ссылкой на живой recon.

Верификация окружения

  • git diff origin/main...6cf8e5b — совпадает с описанием PR.
  • pytest -q на чистом PR head (без незакоммиченных изменений в чекауте): 3348 passed, 23 skipped, 39 subtests passed.
  • black --check / flake8 на изменённых файлах — чисто.

Живая верификация --clear-headline/--clear-text против реальной кампании отложена по описанию PR (обоснованно — сериализованная очередь на общую master-сессию), это не блокирует офлайн-мерж согласно предыдущей практике (#665 тоже смержен офлайн первым).

…ting_value

The commit-check loop added in 6cf8e5b guarded its FIRST inner_text()
read (the pre-click "is it already empty?" check) with try/except
PlaywrightError -> BrowserSessionError, but the post-click reads inside
the poll loop itself were left unguarded. A transient DOM detach during
the poll (Yandex re-rendering the slot row after the .clear handler
runs) would surface a raw PlaywrightError traceback instead of this
function's documented "click may not have committed" error — in the
exact failure mode the commit-check mechanism exists to make legible.

Extract both post-click reads into a helper with the same
PlaywrightError -> BrowserSessionError conversion as the initial read.

Found by cycle-review (Codex adversarial review, round 2) on PR #787.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGxHgqUmX2YeA1ignZSef1
@axisrow

axisrow commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

🔍 Local review (cycle 2) — round 366fb45b-cbbc-413f-89c8-b7904ce96e58

Reviewed locally: built-in /review remained unavailable in this environment this round too; continued Codex-only per prior user sign-off. No GitHub bots pinged.

Verdict Reviewer Finding Location
FIX codex Post-click commit-check reads in the new clear-slot helper were not guarded against a transient DOM detach direct_cli/browser/masters.py:7390

Resolution: fixed in d81b851 — extracted the post-click reads into a helper with the same PlaywrightErrorBrowserSessionError conversion as the initial read, so a transient detach mid-poll surfaces the intended error instead of a raw traceback. Full offline suite green (3348 passed), black/flake8 clean.

@axisrow

axisrow commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

🔍 Local review (cycle 3) — round 35c76f01-8abc-4917-aa08-16fa7c0dc80f

Reviewed locally: built-in /review remained unavailable in this environment; continued Codex-only per prior user sign-off. No GitHub bots pinged.

Verdict: approve, no findings. Codex traced the clear-slot path end-to-end (false-success, partial-failure, and irreversibility scenarios) and found every failure boundary guarded: CLI validation, pre-click already-empty check, post-click commit poll with detach handling (fixed in cycle 2), and post-save re-verification of cleared slots. No new FIX this round — this closes the review cycle.

@axisrow

axisrow commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

📋 Review summary — all cycles

Cycle Reviewer Finding Verdict Resolution
1 codex Clear-slot click was not confirmed to commit to the DOM before the irreversible save FIX Fixed in 6cf8e5b
2 codex Post-click commit-check reads inside the new clear-slot helper were not guarded against a transient DOM detach FIX Fixed in d81b851
3 codex — (approve, no findings)

Totals: 2 FIX (both resolved), 0 SKIP, 0 UNVERIFIED, 0 HALLUCINATION.

Note: the built-in /review command was unavailable for programmatic invocation in this environment across all three rounds (Skill tool refused with disable-model-invocation); per the skill's own rule against replicating a blocked skill's logic by other means, all three rounds proceeded Codex-only, with explicit user sign-off obtained before round 1. No GitHub bots were pinged (local mode).

…llback

test_raises_browser_session_error_when_commit_check_read_detaches (d81b851)
asserted assertNotIsInstance(ctx.exception, PlaywrightError). CI runs
without the playwright package installed, where masters.py's own
ImportError guard makes PlaywrightError an alias for bare Exception —
so the assertion became "BrowserSessionError is not an Exception",
which is always false, failing the test in exactly the environment
this project's CI uses (confirmed: local run has playwright installed
and passed, CI does not and failed with the same message reported
here).

assertRaises(BrowserSessionError) already proves the exception IS a
BrowserSessionError; replace the redundant-and-environment-dependent
check with an exact-type assertion, which needs no PlaywrightError
reference and is stable regardless of whether playwright is installed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGxHgqUmX2YeA1ignZSef1
@axisrow

axisrow commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

🔍 Local re-review (after CI-fix commit) — round 76f9e41b-cdf5-49c0-9a31-7e071d206064

Triggered by a post-review CI-fix push (30866d3 — a test assertion relied on PlaywrightError's playwright-absent fallback to bare Exception, which is exactly the environment this repo's CI runs in; replaced with an exact-type assertion). This is a re-review of the same cycle, not a new review cycle.

Reviewed locally: built-in /review remained unavailable in this environment; continued Codex-only per prior user sign-off. No GitHub bots pinged.

Verdict: approve, no findings. Codex re-traced the full clear-slot path against the new head (including compaction/reordering scenarios, partial-failure/retry safety, and the CLI-vs-browser-layer overlap-validation boundary) and found no material issue — every failure path fails closed before the irreversible save or is caught by post-save verification.

@axisrow
axisrow merged commit 027c8b1 into main Aug 6, 2026
6 checks passed
@axisrow
axisrow deleted the feat/648-stage-b-weighted-lists branch August 6, 2026 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant