Skip to content

BOGO Select for WooCommerce 2.3.6

Choose a tag to compare

@johnjanney johnjanney released this 02 Aug 04:28
· 29 commits to main since this release

Install: download bogo-select-2.3.6.zip below, then WordPress → Plugins → Add New → Upload Plugin.

[2.3.6] — 2026-08-02

The measurement, and what it turned out to be worth.

CODEX-REVIEW.md M-03 asked for a benchmark before any latency claim was
published. Running it found a broad gift search costing 612 database queries on
a 2,000-product catalogue; fixing what it found brought that to 15. The runtime
change is one batched fetch — behaviour is identical and only the cost moved,
which makes this a PATCH.

Changed

  • A page of gift choices is fetched in one batch instead of one product at a
    time
    (CODEX-REVIEW.md M-03). The benchmark below found a broad All
    Products search costing 612 database queries against 2,000 products — about
    three per candidate, because each wc_get_product() found its own way to the
    post row, its meta, and its product_type and product_visibility terms.
    2.3.1's request memo stops the same product being loaded twice inside a
    request and does nothing about the request being the first one, which on a
    store without a persistent object cache is every request.

    _prime_post_caches() now asks for all of it once, before the eligibility
    loop starts. Re-measured on the same catalogue: 612 queries → 15 for a
    broad search, 1,508 → 11 for the curated list's cold eligibility build,
    81 → 12 for browsing a page. A single-SKU search is unchanged at 12,
    which is the guard working — a batch of one is skipped, since priming it
    would cost a query to save none.

    Nothing downstream changed: the same products are loaded, by the same calls,
    in the same order. This is not fewer loads, it is the same loads costing
    fewer queries — which is why the unit suite's product-load counts are
    untouched, and why the cost needed a benchmark to see at all.

    M-03 floated a result cache keyed by search term and catalogue state if
    object reuse turned out not to be enough. This is the cheaper answer to the
    same measurement: no new cache to invalidate, no key to get wrong, nothing to
    go stale.

Added

  • A large-catalogue benchmark, and the numbers from running it
    (CODEX-REVIEW.md M-03). It asked for wall time, database queries, CPU, and
    peak memory measured before any latency claim is published, and was explicit
    that the product-load counts the unit suite holds are not latency. These are
    seconds, queries, and bytes; the loads stay where they were.

    On 2,000 products with 500 curated, no persistent object cache: a broad All
    Products search costs 0.23s and 612 queries cold and 0 queries warm; the
    curated list's cold eligibility build costs 0.48s and 1,508 queries, carried
    by a transient so a store pays it once per ten minutes rather than per
    request; browsing a page costs 0.03s and 81 queries.

    The warm column is 2.3.1's request memo working — zero queries on the second
    call, every path. The cold column was the finding: about three queries per
    candidate, because the memo stops a product being loaded twice in a request
    and does nothing about the request being the first. That is now fixed — see
    below — and both sets of numbers are recorded in CODEX-REVIEW-RESPONSE.md
    with what they do and do not say.

    Its own workflow, on workflow_dispatch. Seeding takes about a minute and
    the numbers are for reading rather than gating; a threshold on a shared runner
    would mostly measure the runner.

  • The settings screen is exercised through options.php under a real role
    (CODEX-REVIEW.md L-02, and the regression test M-02 asked for).
    AdminSettingsTest.php calls the same sanitize callback WordPress calls, and
    structurally cannot reach what happens before it: the nonce, the option
    allowlist, and the capability check. That check was the whole of M-02 — a Shop
    Manager could fill the form in and be refused on submit.

    A Shop Manager now signs in, opens the page, saves a schedule, and the form is
    read back to prove what was stored. An Editor is refused the page. A malformed
    date and a reversed window are submitted and the previous schedule is shown to
    have survived both — reading the form rather than the message, since a screen
    that showed an error and stored the value anyway is exactly what M-01 was.

    The site runs on a non-UTC clock for it, and the dates come from the store's
    own current_time(), so DECISION.md D-019's "whole days in the site's
    timezone" is exercised rather than assumed: an offer ending today is not
    called expired, and one ending yesterday is.