Reviewer cross-check of 2ce59eb on real dumps found a Samsung sample where
the true bottom bar is a label-less 31px FrameLayout whose only text node
is zero-size ([0,0][0,0]). A whole-panel leaf scan fell through to the
next lowest labelled leaf, which lives in the PAYMENT-METHOD row directly
above the bottom bar ("使用#微信支付,更换先用后付可0元下单"), and the
climb-to-clickable-ancestor landed the click on "change payment method"
instead of the order button. That must never happen.
finalSubmitTargets is now a strict two-step "row, then leaf" pick instead
of a single whole-panel leaf scan:
1. Row: among visible/enabled/non-zero-size CLICKABLE nodes inside the
recognized panel's container with height <= 30% of screen height (the
guard from 2bc624f), pick the one with the lowest bottom edge; ties go
to the rightmost. This row, and only this row, may hold the order
button. If its own subtree matches a payment-method alias
(textAliases.specPanel.paymentAreaAliases: 微信支付/先用后付/支付方式) or
SUBMIT_TARGET_BLOCKED_MARKERS, fail explicitly (outcome=row_blocked) -
never fall back to a higher row.
2. Leaf: within that row's own subtree (or the row itself), among
visible/non-zero-size nodes with a non-blank own label whose nearest
clickable ancestor is EXACTLY that row, pick the bottom-right-most one
(unchanged rationale from 2ce59eb: PurchaseUiDriver.clickFresh re-finds
by preferredOrDescendantLabel()+className+center±32 before climbing to
the nearest clickable ancestor, so the target must carry a real label).
If the row has no such leaf, fail explicitly (outcome=bottom_row_unlabelled)
- never fall back to a higher row either.
hasFinalSavedAddressEvidence, finalConfirmation and submitOrderOnce still
all consume this one finalSubmitTargets.
Tests: SpecPanelFixtures.sheet(Sheet(submit = "hidden")) already models the
Samsung shape (31px hidden bottom bar above a real payment-method row) -
added a test asserting it now fails explicitly with no click, verifying
the payment row is never touched. Added a second test where the payment
row is itself the bottom-most clickable row (no submit region at all) and
must be rejected by its own alias match. Fixed readySheet() (used by the
address-save-wait test) to stop compounding liveShapedSheet()'s own hidden
31px placeholder with a separate real submit button - the two used to tie
and could flip which one the rightmost tie-break picked; it now builds the
same shape directly with submit="none" so "r/final" is the sole,
unambiguous bottom row. Updated one diagnostic key assertion (tie= ->
rowTie=) for the row-level tie-break. Every other #335 test (legacy
提交订单, 570 structure + ReFindingDriver re-find/climb, payment-word
blocked, outside-panel-container, no-determinable-container) passed
unmodified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F