Skip to content

fix(TWO-25554): retire a whole capture, scope the write record, and give each panel its own notice - #414

Merged
dgjlindsay merged 23 commits into
stagingfrom
doug/TWO-25554-panel-fixes
Sep 3, 2026
Merged

fix(TWO-25554): retire a whole capture, scope the write record, and give each panel its own notice#414
dgjlindsay merged 23 commits into
stagingfrom
doug/TWO-25554-panel-fixes

Conversation

@dgjlindsay

Copy link
Copy Markdown
Contributor

Closes the review findings left open after #410 and #411 merged. Deliberately carries no change to how "is billing a distinct address" is answeredbillingIsDistinct() is still staging's own isVisible($(BILLING_FIELD_SELECTOR)). That question is a separate decision and is on its own PR.

view/frontend/web/js/model/company-source-resolver.js is code-identical to staging; the only difference in the whole file is comments.

What this closes

clear() retired only half a capture. It reset the company name, number and source but left soleTraderAdopted, captureMode and addressNotice standing, so re-checking "same as shipping" left the billing panel to remount in sole-trader mode over an empty identity. It now retires all of them (soleTraderAvailable stays — it is a property of the country, not of the capture). The test that claimed to pin this asserted only the name and number, so it passed while the adoption survived; it now asserts the mode and the adoption too.

A moved mount left a stale combobox behind. Chrome removal already fired on a mount move, but nothing stripped role, aria-haspopup, aria-controls and aria-expanded off the field the panel had left, so screen readers were offered a combobox that no longer had a popover. One helper now does it, shared by the move path, releaseField() and unmount().

The address write record was keyed on the form element. Fire Checkout and Amasty replace the whole payments-list subtree — which is where the billing form lives — so the key died with the fieldset and a later retraction found nothing on record. It is now keyed on the calling panel's own identity, which survives the DOM being replaced and which no other panel holds a reference to.

Address notices crossed panels. snapshot() copied an identity's whole state, so addressNotice travelled through the resolver, and the payment tile — bound to the resolved identity — was the only renderer of it anywhere. A shipping-side failure while billing was authoritative displayed nothing at all, and a billing-side failure surfaced in the tile with copy pointing at the wrong form. Each panel now renders its own notice at its own field, through the same chrome mechanism as the company number and the sole-trader link, and snapshot() carries only the company name, number and source.

Billing had no write destination of its own. Its address and telephone write-backs went to a raw form-root selector while shipping used a write target that announces when there is nowhere to write. Billing now has the matching pair, so a sole-trader write-back with the fieldset gone tells the buyer instead of failing silently. That refusal gets its own string rather than reusing one that says "please enter it below" on the path where there is no form to enter it into.

Three tables' row descriptions never reached an assertion. The callback took (actor, fixture), so the description formatted the fixture object and vanished. Converted to the tagging helper, as the rest of the suite already does.

Also removed: four guards shaped around a test double's deficiency, with the comments that claimed a double required them; and an undiscriminated multi-match check, after establishing by construction that the layout processor injects exactly one company_id per form root.

Tests

53 suites / 929 tests. Every one of the 22 commits green on its own.

Five previously-unpinned invariants now have discriminating tests, each proven by applying the mutation that should break it. Three vacuity classes were found and fixed while getting here: negative assertions running before the setTimeout(0) identity publish they denied; a write reusing a value the identity already held, so no notify fired and a stale mirror satisfied the assertion; and a harness stub returning a constant empty string, which had silently made every fallback-country assertion vacuous.

No production PHP changes — the only PHP touched is the layout-processor test, which gains rows for the widened billing predicate's own false-positive shapes.

Still owed

Real nb/nl/sv translations for one new string, currently echoing its English source at i18n/nb_NO.csv:314, nl_NL.csv:310, sv_SE.csv:311.

No post-change browser verification — the staging shop runs staging.

dgjlindsay and others added 22 commits September 1, 2026 18:30
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dentity

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…re diff

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tives

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ess form

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e harness's divergences

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…related one

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… failure diff

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ount moves

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The mirror carries the company fields alone, so no panel's UI state
travels into the identity the tile and order-intent read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ding

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…xport

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…carries

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The jQuery stub defines length, find and closest, so the missing-method
cases soleAddressForm's guards claimed to cover cannot occur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ding

Each fails under a mutation of the guard it names: the two identity guards in
company-search, the quote's no-billing-address branch, _chromeNode's
sibling-only scoping, and the billing panel's own-root country fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_ownFormRoot's ceiling is the panel's own address form, and the layout
processor injects exactly one company_id field per form, so no ancestor
inside that ceiling can hold two.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed the diff (JS panel/chrome/address-write fixes + accompanying tests, plus the layout-processor PHP test). Traced through the key changed paths:

  • company-identity.js clear() now retires soleTraderAdopted/captureMode/addressNotice alongside name/id, with the guard updated to match; snapshot() correctly narrows to just the company fields.
  • company-search.js addressWriteRecord re-keyed from the form element to the calling panel's identity (WeakMap) — survives subtree replacement, and each call site (company-capture.js, address-autocomplete.js, gateway sole-trader writeback) passes the right identity through.
  • company-capture-component.js refreshMount(): chrome removal now happens before _boundSelector is reassigned, and fieldNode()/_chromeHost() read off _boundSelector, so _removeChrome() correctly targets the old host being left, not the new one. onCountryChanged() reads isSoleTrader() before clear() resets the mode, fixing the round-trip-back-into-search bug described in the PR.
  • announceAddressUnavailable (fetch failure) and the new announceAddressUndeliverable (no destination) are kept distinct and each call site was updated consistently — no orphaned references.
  • The billing/shipping write-target split (billingWriteRoot/billingWriteTarget) mirrors the existing shipping helpers and keys off live mountSelector() rather than static selector matching, matching the "hidden fieldset" scenario described.

No security, correctness, or breaking-API issues found. Test coverage for each fix (retirement, notice scoping, write-record keying, chrome cleanup on move) looks thorough and each is tied to a specific regression scenario.

Ready for human approval ✅

Costs nothing and guards against other faults that could cause
inadvertent double rendering, independent of _ownFormRoot's ceiling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only one commit landed since the prior review (2de9bb1): it restores the "exactly one restored-number match" guard in _restoredCompanyNumber() (company-capture-component.js), reverting an earlier removal. It's a narrow, defensive change — climbs to the next ancestor unless exactly one match is found under the current one — and is now pinned by the two new fixtures added to LayoutProcessorPluginTest.php::untouchedBillingNodeProvider(). No new risk introduced.

The rest of the diff (capture retirement in clear(), chrome-cleanup ordering in refreshMount(), the address-write record re-keyed from DOM element to panel identity via WeakMap, and per-panel address notices replacing the shared tile notice) matches the previous review's findings — no security, correctness, or breaking-API issues.

Ready for human approval ✅

@dgjlindsay
dgjlindsay merged commit 6479296 into staging Sep 3, 2026
30 checks passed
@dgjlindsay
dgjlindsay deleted the doug/TWO-25554-panel-fixes branch September 3, 2026 09:09
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