feat(core): read variations out of the WooCommerce cart form (phase 5) - #18
Merged
Conversation
Chrome 151 accepts the flag on the command line, prints no error, and does nothing. The browser starts and the extension is simply absent. The reason this is worth a section rather than a footnote is that every symptom points at a broken build: no toolbar icon, chrome://extensions listing only Chrome's own component extensions, and the extension's URL returning ERR_BLOCKED_BY_CLIENT, which reads like it crashed rather than like it was never loaded. That is half an hour spent debugging a package that was fine, which is exactly the kind of thing publishing.md already exists to stop — it is the same lesson as the note above it about web-ext and AMO's validator disagreeing. Also records the asymmetry that catches people between the two routes that do work: loading unpacked in Chrome wants the directory, Firefox's temporary add-on wants the manifest file inside it. The consequence worth knowing before phase 15 rather than during it: any job that wants to drive a browser with this extension pre-loaded — CI, an end-to-end test, the app work — cannot use a command-line flag and needs a real automation harness instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 5 has been "partial" since it was written, and this is the gap. Layers A and B already produce variations when the shop answers its own API or publishes a `ProductGroup`. Neither is guaranteed. What is effectively guaranteed on a WooCommerce variable product is the add-to-cart form, which carries every variation as JSON in one attribute — `WC_Product_Variable::get_available_variations()`, with per-variation price, SKU, stock, image, weight and dimensions, already in the page, needing no extra request and surviving a shop with its REST API switched off. `variations.ts` was written earlier and never wired in. This connects it to Layer B, adds the fixture and the tests, and closes the phase. **Where it sits in the reading order is the decision worth recording.** `mergeDraftInto` deliberately refuses to reconcile two sources' variant lists — two sources listing variations in different orders or granularities cannot be merged without guessing, and a guess duplicates rows in the export. So whichever source is reached first owns them outright. The form goes *second*, after JSON-LD: a page that already publishes a `ProductGroup` keeps behaving exactly as it did, and the form fills the far more common case where JSON-LD described the parent and said nothing about its variations. Putting it first would have silently changed the answer for every page that had both. The trap it exists to handle is §7.6. The form keys attributes by term slug — `500g` — while the parent's options are term names — `۵۰۰ گرم`. WooCommerce silently drops every variation on import unless the two match character-for-character, so the `<select>` is read first as the translation table and the slugs are mapped back through it. Tests cover both stages, because they fail differently. The unit tests assert the reading; the pipeline tests assert the CSV, which is where §7.6 actually bites — parent followed by its own variations, linked by SKU and never by ID, one attribute value per variation row and every one of them spelled exactly as the parent lists it, parent price cells empty rather than zero, and attributes local by default. 18 new tests, 831 total. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Phase 5 has been
partialsince it was written, and this closes the gap.Layers A and B already produce variations when the shop answers its own API or publishes a schema.org
ProductGroup. Neither is guaranteed. What is effectively guaranteed on a WooCommerce variable product is the add-to-cart form, which carries every variation as JSON in one attribute:That blob is
WC_Product_Variable::get_available_variations()— per-variation price, sale price, SKU, stock, image, weight and dimensions — already in the page, needing no extra request, and surviving a shop with its REST API switched off. Reading it is the difference between exporting a variable product as one priceless row and exporting it correctly.variations.tswas written in an earlier session and never wired in. This connects it to Layer B, adds the fixture and the tests, and moves phase 5 todone.The decision worth reviewing
Where the form sits in the reading order.
mergeDraftIntodeliberately refuses to reconcile two sources' variant lists — two sources listing variations in different orders or granularities cannot be merged without guessing, and a guess duplicates rows in the export. So whichever source is reached first owns the variants outright.The form goes second, after JSON-LD. That means a page already publishing a
ProductGroupkeeps behaving exactly as it did, and the form fills the far more common case where JSON-LD described the parent and said nothing about its variations. Putting it first would have silently changed the answer for every page that had both — which is the kind of change that passes CI and shows up as a wrong export weeks later.The trap it exists to handle
§7.6. The form keys attributes by term slug (
attribute_pa_weight: "500g") while the parent's options are term names (۵۰۰ گرم). WooCommerce silently drops every variation on import unless the two match character-for-character, so the<select>is read first as the translation table and the slugs mapped back through it. This is the same trap the Store API adapter hit in phase 3.How it was verified
npm run checkpasses. 831 tests, 18 of them new.Tested at both stages, because they fail differently:
packages/core/test/extract/variations.test.ts) — the reading itself: slug→name mapping, one axis value per variation, per-variation price/SKU/stock, out-of-stock carried as out-of-stock, and the two degraded cases (WooCommerce writingdata-product_variations="false"when its template opts out, and unreadable JSON) reported rather than swallowed.packages/exporters/test/pipeline.test.ts) — the CSV, which is where §7.6 actually bites. Parent followed immediately by its own variations (§7.4), linked by SKU and never by ID (§7.2), one attribute value per variation row with every one spelled exactly as the parent lists it (§7.6), parent price cells empty rather than zero (§7.5), attributes local by default (§7.7), every SKU unique.The fixture is a Persian variable product whose JSON-LD describes only the parent — deliberately, so the tests prove the form fills a real gap rather than a contrived one.
One regression check included explicitly:
readVariationFormnow runs on every Layer B page, so there is a test asserting a page with no variation form is completely undisturbed.Note for the reviewer
While writing these I asserted
parentSkuimmediately after extraction and it failed. That was the test being wrong, not the code — §7.3 hasassignSkusrun at the export step, because it needs to see a whole scan at once to guarantee uniqueness across pages. The test now runsassignSkusfirst and asserts the link where it is actually made. Worth knowing if you read that test and wonder why it is not simpler.Checklist
npm run checkpasses (format, lint, typecheck, tests)🤖 Generated with Claude Code