Skip to content

fix(frontend): Escape leaves stale purchase buckets for the next modal submission #331

Description

@cristim

Summary

Closing the purchase modal with Escape bypasses purchase-state cleanup. A later single-bucket modal can submit the previous fan-out buckets instead of the newly displayed recommendation.

Discovered during independent Claude Fable 5.1 review of LeanerCloud/cloud-commitments-cli#2071. This predates that PR and remains present on main at eac9a62a88d49cbb30abf1dc3943037dd1d58b0b.

Current behavior and reproduction

  1. Select recommendations forming at least two supported buckets and open Purchase.
  2. Press Escape without submitting.
  3. Select a different single-bucket recommendation and open Purchase again.
  4. Click Send for Approval and inspect the confirmation/request payloads using a fixture backend, not real cloud purchases.

frontend/src/modal.ts:107-110 calls only closeModal(el) on Escape. The explicit close-button handler in frontend/src/app.ts:245-249 additionally calls clearPurchaseModalRecommendations() and clearFanOutBuckets(). openPurchaseModal sets single-row state without clearing the old fan-out state. handleExecutePurchase checks getFanOutBuckets() first, so the old buckets win over the visible single-row selection.

Expected behavior

Every supported close path must discard purchase-specific state. Reopening a modal must submit exactly the selection displayed in that modal.

Proposed fix and verification

Route Escape and explicit close through the same purchase cleanup without changing unrelated modal behavior. Add a regression that opens fan-out, presses Escape through the real keyboard handler, opens a different single-bucket selection, and asserts on the actual intercepted executePurchase payload. Verify it fails before the fix and passes afterward.

Also make the submit router distinguish no fan-out state (null) from an all-skipped fan-out ([]) introduced by LeanerCloud/cloud-commitments-cli#2071. The latter must not fall through into stale single-row state. This second path is currently prevented by a disabled button; do not describe it as a confirmed live exploit.

Evidence and limits

Static review confirmed the control flow against the committed code. This follow-up has not yet been reproduced in a browser. Approval is still required for fan-out submissions, so the demonstrated code path concerns unintended approval requests, not an observed cloud charge.

References: LeanerCloud/cloud-commitments-cli#2071, LeanerCloud/cloud-commitments-cli#1903, LeanerCloud/cloud-commitments-cli#1904, and LeanerCloud/cloud-commitments-cli#2071 (comment).

Severity: high when stale purchase intent is submitted; limited audience and an explicit-close workaround. Kept separate from the in-flight button regression being repaired in LeanerCloud/cloud-commitments-cli#2071.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions