{"id":17,"date":"2026-10-04T17:17:00","date_gmt":"2026-10-04T21:17:00","guid":{"rendered":"https:\/\/mousabatarseh.com\/execution-space-proposal\/cutover-check\/"},"modified":"2026-10-05T01:24:26","modified_gmt":"2026-10-05T05:24:26","slug":"cutover-check","status":"publish","type":"page","link":"https:\/\/mousabatarseh.com\/execution-space-proposal\/","title":{"rendered":"Cutover Check"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"17\" class=\"elementor elementor-17\">\n\t\t\t\t<section class=\"elementor-element elementor-element-3ce4b93 e-con-full cc hero e-flex e-con e-parent\" data-id=\"3ce4b93\" data-element_type=\"container\" data-e-type=\"container\" id=\"top\">\n\t\t\t\t<div class=\"elementor-element elementor-element-05b27e5 cc elementor-widget elementor-widget-cc-hero-field\" data-id=\"05b27e5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-hero-field.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"hero__field\" data-cc-field=\"hero\"><canvas class=\"hero__canvas\" aria-hidden=\"true\"><\/canvas><p class=\"hero__side hero__side--source label\" aria-hidden=\"true\">Old store<span class=\"hero__platform\"> \u00b7 WooCommerce<\/span><\/p><p class=\"hero__side hero__side--target label\" aria-hidden=\"true\">New store<span class=\"hero__platform\"> \u00b7 Shopify<\/span><\/p><p class=\"hero__seam label\" aria-hidden=\"true\">Cutover<\/p><script type=\"application\/json\" data-cc-hero>{\"count\":412,\"drifted\":65,\"human\":2,\"missing\":0,\"marks\":null,\"lists\":{\"drifted\":[\"DW-1001-CHR \u00b7 Harbor Enamel Mug \u00b7 case pack\",\"DW-1015-NVY \u00b7 Ember Camp Tumbler \u00b7 case pack\",\"DW-1016-CRM \u00b7 Ridgeline Steel Bottle \u00b7 case pack\",\"DW-1019-CHR \u00b7 Cedar Steel Bottle \u00b7 handle\",\"DW-1022-CHR \u00b7 Trailhead Steel Bottle \u00b7 case pack\",\"DW-1031-RST \u00b7 Ember Canteen \u00b7 handle\",\"DW-1035-NVY \u00b7 Cedar Trail Flask \u00b7 case pack\",\"DW-1043-NVY \u00b7 Cedar Pint Cup \u00b7 case pack\",\"DW-1055-RST \u00b7 Ember Travel Press \u00b7 case pack\",\"00401 \u00b7 Ridgeline Cast Iron Skillet \u00b7 SKU\",\"00402 \u00b7 Harbor Cast Iron Skillet \u00b7 SKU\",\"00403 \u00b7 Juniper Cast Iron Skillet \u00b7 SKU\",\"00404 \u00b7 Cedar Cast Iron Skillet \u00b7 SKU\",\"00405 \u00b7 Granite Cast Iron Skillet \u00b7 SKU\",\"00406 \u00b7 Lakeside Cast Iron Skillet \u00b7 SKU\",\"00407 \u00b7 Trailhead Cast Iron Skillet \u00b7 SKU\",\"00408 \u00b7 Ember Cast Iron Skillet \u00b7 SKU\",\"00409 \u00b7 Ridgeline Kettle \u00b7 handle, SKU\",\"00410 \u00b7 Harbor Kettle \u00b7 SKU\",\"00411 \u00b7 Juniper Kettle \u00b7 SKU\",\"00412 \u00b7 Cedar Kettle \u00b7 SKU\",\"00413 \u00b7 Granite Kettle \u00b7 SKU\",\"00414 \u00b7 Lakeside Kettle \u00b7 SKU\",\"00415 \u00b7 Trailhead Kettle \u00b7 SKU\",\"00416 \u00b7 Ember Kettle \u00b7 SKU\",\"00417 \u00b7 Speckled Enamel Mug 12 oz, Case of 24 \u00b7 handle, case pack, SKU\",\"00418 \u00b7 Harbor Dutch Oven \u00b7 SKU\",\"00419 \u00b7 Juniper Dutch Oven \u00b7 SKU\",\"00420 \u00b7 Cedar Dutch Oven \u00b7 SKU\",\"00421 \u00b7 Granite Dutch Oven \u00b7 SKU\",\"00422 \u00b7 Lakeside Dutch Oven \u00b7 SKU\",\"00423 \u00b7 Trailhead Dutch Oven \u00b7 SKU\",\"00424 \u00b7 Ember Dutch Oven \u00b7 SKU\",\"CK-1039 \u00b7 Ember Cutting Board \u00b7 handle\",\"BL-1033-TW-CHR \u00b7 Harbor Stadium Blanket \u00b7 handle\",\"AP-1003-S-RST \u00b7 Cedar Flannel Shirt \u00b7 handle\",\"AP-1012-S-NVY \u00b7 Ridgeline Field Tee \u00b7 handle\",\"AP-1025-S-MOS \u00b7 Harbor Chore Coat \u00b7 handle\",\"AP-1069-S-SND \u00b7 Birch Camp Sock \u00b7 handle\",\"CN-1020-08 \u00b7 Birch Tin Candle \u00b7 handle\",\"CN-1035-08 \u00b7 Juniper Fire Starter \u00b7 case pack\",\"CN-1039-08 \u00b7 Trailhead Fire Starter \u00b7 case pack\",\"BG-1033-CHR \u00b7 Ember Tote \u00b7 handle\",\"BG-1036-CHR \u00b7 Summit Tote \u00b7 handle\",\"BG-1038-CRM \u00b7 Foxfire Tote \u00b7 handle\",\"BG-1041-SND \u00b7 Juniper Hip Pack \u00b7 handle\",\"BG-1044-RST \u00b7 Lakeside Hip Pack \u00b7 handle\",\"LT-1006-SND \u00b7 Trailhead Lantern \u00b7 handle\",\"LT-1017 \u00b7 Trailhead Headlamp \u00b7 handle\",\"LT-1023 \u00b7 Harbor String Light \u00b7 handle\",\"LT-1029 \u00b7 Ember Headlamp \u00b7 handle\",\"PG-1002 \u00b7 Juniper Field Notebook \u00b7 handle\",\"PG-1003 \u00b7 Cedar Field Notebook \u00b7 case pack\",\"PG-1004 \u00b7 Granite Field Notebook \u00b7 case pack\",\"0850031700486 \u00b7 Ember Field Notebook \u00b7 SKU\",\"PG-1011 \u00b7 Driftwood Field Notebook \u00b7 handle\",\"PG-1013 \u00b7 Ridgeline Trail Map \u00b7 handle\",\"PG-1017 \u00b7 Granite Trail Map \u00b7 case pack\",\"PG-1022 \u00b7 Birch Trail Map \u00b7 handle\",\"PG-1025 \u00b7 Foxfire Trail Map \u00b7 case pack\",\"PG-1028 \u00b7 Juniper Card Set \u00b7 case pack\",\"0850031700523 \u00b7 Foxfire Card Set \u00b7 SKU\",\"PG-1039 \u00b7 Ridgeline Wall Calendar \u00b7 case pack\",\"0850031700449 \u00b7 Harbor Wall Calendar \u00b7 SKU\",\"0850031700412 \u00b7 Lakeside Wall Calendar \u00b7 SKU\"],\"human\":[\"PG-1019 \u00b7 Trailhead Trail Map \u00b7 zero price\",\"PG-1030 \u00b7 Granite Card Set \u00b7 zero price\"],\"missing\":[]},\"state\":\"stale\",\"run_id\":101,\"verdict\":\"BLOCK\"}<\/script><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e6951fc cc elementor-widget elementor-widget-cc-hero\" data-id=\"e6951fc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-hero.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"hero__body wrap\" data-cc-hero-body><h1 class=\"hero__title\" id=\"hero-title\"><span class=\"hero__line\">The counts match.<\/span> <span class=\"hero__line\" data-cc-hero-line=\"drifted\"><span class=\"hero__num\"><span class=\"count\" data-cc-count>65<\/span><\/span> products don\u2019t.<\/span><\/h1><div class=\"hero__intro\"><p class=\"hero__sub\">A working cutover check for Shopify migrations. I built it as my application for Execution Space\u2019s contract Shopify developer role.<\/p><div class=\"hero__actions\"><a class=\"button\" href=\"#demo\">Open the check <svg class=\"arrow\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" style=\"transform:rotate(90deg)\"><path d=\"M2 8h10M8.5 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"square\"><\/path><\/svg><\/a><button type=\"button\" class=\"button button--ghost\" aria-pressed=\"false\" data-cc-action=\"focus-flagged\">Show only the flagged<\/button><\/div><\/div><section class=\"window hero__readout\" aria-label=\"Reconciliation summary\" data-cc-window=\"readout\"><header class=\"window__bar\"><span class=\"window__title\" data-cc-title>reconcile-catalog<\/span><span class=\"window__status\" data-cc-status><span class=\"chip chip--mute cc-mode\" data-cc-mode=\"stale\">stale<\/span> stale \u00b7 05:20 UTC<\/span><\/header><div class=\"window__body\"><ul class=\"readout\" data-cc-readout><li>412 products matched \u00b7 1,186 variants matched \u00b7 0 lost<\/li><li><strong>68 fields wrong<\/strong> on 65 products<\/li><li>2 products need a person<\/li><li>Gate: <strong class=\"readout__verdict readout__verdict--block\">BLOCK<\/strong><\/li><li><span class=\"cc-state\">Stale \u00b7 last successful comparison 05:20 UTC, 5 Oct 2026 (run #101). fetched more than 15 minutes ago<\/span><\/li><\/ul><\/div><\/section><p class=\"hero__caption\" data-cc-caption data-cc-caption-one=\"Each mark is one product.\" data-cc-caption-many=\"Each mark stands for about %s products.\" data-cc-caption-rest=\"A lit mark arrived wrong. A ring needs a person. Independent work on a synthetic catalog, not affiliated with Execution Space.\" data-cc-caption-missing=\"A hollow square stopped at the cutover is a record missing on the new store.\">Each mark is one product. A lit mark arrived wrong. A ring needs a person. Independent work on a synthetic catalog, not affiliated with Execution Space.<\/p><script type=\"application\/json\" data-cc-strings=\"hero\">{\"line_1\":\"The counts match.\",\"line_drifted_many\":\" products don\u2019t.\",\"line_drifted_one\":\" product doesn\u2019t.\",\"line_review_many\":\" still need a person.\",\"line_review_one\":\" still needs a person.\",\"line_clean\":\"So does every product.\",\"line_not_run\":\"No comparison has run yet.\",\"readout_matched\":\"%1$s products matched \u00b7 %2$s variants matched \u00b7 %3$s lost\",\"readout_fields\":\"\\u003Cstrong\\u003E%1$s fields wrong\\u003C\/strong\\u003E on %2$s products\",\"readout_human\":\"%s products need a person\",\"readout_gate\":\"Gate:\",\"readout_not_run\":\"No comparison has run yet. The readout fills in after the first run.\",\"readout_verdict_not_run\":\"NOT RUN\",\"readout_state_stale\":\"Stale \u00b7 last successful comparison %1$s (run #%2$s). %3$s\",\"readout_state_unverified\":\"Unverified \u00b7 fetched %1$s (run #%2$s). %3$s\",\"caption_one\":\"Each mark is one product.\",\"caption_many\":\"Each mark stands for about %s products.\",\"caption_rest\":\"A lit mark arrived wrong. A ring needs a person. Independent work on a synthetic catalog, not affiliated with Execution Space.\",\"caption_missing\":\"A hollow square stopped at the cutover is a record missing on the new store.\",\"readout_status_live\":\"live \u00b7 %s\",\"readout_status_stale\":\"stale \u00b7 %s\",\"readout_status_unverified\":\"unverified\",\"readout_status_not_run\":\"not run\"}<\/script><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t<section class=\"elementor-element elementor-element-55a592f e-con-full cc band e-flex e-con e-parent\" data-id=\"55a592f\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-f58e7a7 e-con-full cc wrap band__grid e-flex e-con e-child\" data-id=\"f58e7a7\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-3987201 cc elementor-widget elementor-widget-cc-section-head\" data-id=\"3987201\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-section-head.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"band__lead\"><p class=\"label label--ink\"><span class=\"label__row\">Row 001<\/span><span aria-hidden=\"true\"> \u00b7 <\/span>The question<\/p><h2 class=\"band__title\" id=\"question-title\">Counts show how many arrived. They cannot show what changed.<\/h2><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ef4aafe cc band__text elementor-widget elementor-widget-text-editor\" data-id=\"ef4aafe\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Your <a href=\"https:\/\/executionspace.com\/commerce\/migrate\/\" target=\"_blank\" rel=\"noopener noreferrer\">migration page<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a> says to reconcile the counts, then \u201cspot-check the edges\u201d. That is the right order.<\/p><p>This page is what I would add as your contractor. It runs that spot-check on every record and every mapped field, and gives each failure a reason and an owner. When the full check comes back clean, the spot-check has little left to find.<\/p><p>Everything below runs in your browser on a synthetic catalog. Nothing of yours or your clients\u2019 is connected.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t<section class=\"elementor-element elementor-element-49dc647 e-con-full cc demo e-flex e-con e-parent\" data-id=\"49dc647\" data-element_type=\"container\" data-e-type=\"container\" id=\"demo\">\n\t\t<div class=\"elementor-element elementor-element-3313315 e-con-full cc wrap e-flex e-con e-child\" data-id=\"3313315\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9872927 cc elementor-widget elementor-widget-cc-section-head\" data-id=\"9872927\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-section-head.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<header class=\"section-head\"><p class=\"label\"><span class=\"label__row\">Row 002<\/span><span aria-hidden=\"true\"> \u00b7 <\/span>The demonstration<\/p><h2 class=\"section-head__title\"><span id=\"demo-title\">Change one rule. Watch one record.<\/span><\/h2><div class=\"section-head__intro\"><p>The panel holds six decisions a migration script makes about this catalog. It opens in a state I would call plausible for a careful team: three decisions right, three quietly wrong. Flip any of them and everything beside it is recalculated from the catalog: the record, the ledger, the redirect map and the gate.<\/p><\/div><\/header>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9ff65ec cc elementor-widget elementor-widget-cc-demo\" data-id=\"9ff65ec\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-demo.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"demo__grid\" data-cc-demo><aside class=\"demo__rail\" aria-label=\"Rules and gate\"><section class=\"window rules-window\" id=\"rules\" aria-label=\"Cutover gate and migration rules\" data-cc-window=\"rules\"><header class=\"window__bar\"><span class=\"window__title\" data-cc-title>migration.rules<\/span><span class=\"window__status\" data-cc-status><span class=\"chip chip--mute cc-mode\" data-cc-mode=\"stale\">stale<\/span> 3 of 6 set right<\/span><\/header><div class=\"window__body\"><div class=\"gate\" id=\"gate\" data-verdict=\"BLOCK\" tabindex=\"-1\" data-cc-gate-panel><p class=\"label gate__label\">Gate \u00b7 68 blocking \u00b7 2 to review<\/p><p class=\"gate__verdict\">BLOCK<\/p><p class=\"gate__meaning\">Do not cut over. At least one record would reach a customer, a warehouse or a search engine wrong.<\/p><p class=\"gate__meaning cc-reason\" data-cc-reason data-cc-stale=\"1\">Stale: fetched more than 15 minutes ago. Re-run the comparison before relying on this verdict.<\/p><details class=\"gate__why\"><summary>Why this verdict (4)<\/summary><ul class=\"gate__lines\"><li data-severity=\"block\">28 SKUs changed in transit<\/li><li data-severity=\"block\">24 redirects point at a missing page<\/li><li data-severity=\"block\">16 case products lost the pack size<\/li><li data-severity=\"review\">2 zero prices need a person<\/li><\/ul><p class=\"fineprint cc-connection\" data-cc-connection>Stale \u00b7 Execution Space \u00b7 last successful comparison 05:20 UTC, 5 Oct 2026 (run #101) \u00b7 fetched more than 15 minutes ago \u00b7 Shopify connection OK<\/p><\/details><div class=\"gate__actions\"><button type=\"button\" class=\"button button--small\" aria-disabled=\"false\" data-cc-action=\"fix-all\">Fix all six<\/button><button type=\"button\" class=\"button button--ghost button--small\" aria-disabled=\"true\" data-cc-action=\"reset\">Reset to as found<\/button><\/div><\/div><div class=\"rules__scroll\" data-cc-rules><ul class=\"rules\"><li class=\"rule\" data-on=\"false\" data-cc-rule=\"keepHandles\"><label class=\"rule__label\" for=\"rule-keepHandles\"><input class=\"switch\" type=\"checkbox\" role=\"switch\" id=\"rule-keepHandles\" aria-describedby=\"rule-keepHandles-effect\"\/><span class=\"rule__name\">Keep old handles<\/span><span class=\"rule__count\">24<span class=\"visually-hidden\"> findings<\/span><\/span><\/label><p class=\"rule__effect\" id=\"rule-keepHandles-effect\">Handles are rebuilt from titles. Renamed products get a new URL.<\/p><\/li><li class=\"rule\" data-on=\"false\" data-cc-rule=\"skuAsText\"><label class=\"rule__label\" for=\"rule-skuAsText\"><input class=\"switch\" type=\"checkbox\" role=\"switch\" id=\"rule-skuAsText\" aria-describedby=\"rule-skuAsText-effect\"\/><span class=\"rule__name\">Import SKUs as text<\/span><span class=\"rule__count\">28<span class=\"visually-hidden\"> findings<\/span><\/span><\/label><p class=\"rule__effect\" id=\"rule-skuAsText-effect\">The file passes through a spreadsheet. Numeric SKUs lose leading zeros and long ones turn into scientific notation.<\/p><\/li><li class=\"rule\" data-on=\"true\" data-cc-rule=\"mapSalePrice\"><label class=\"rule__label\" for=\"rule-mapSalePrice\"><input class=\"switch\" type=\"checkbox\" role=\"switch\" id=\"rule-mapSalePrice\" aria-describedby=\"rule-mapSalePrice-effect\" checked=\"\"\/><span class=\"rule__name\">Map sale and regular price<\/span><\/label><p class=\"rule__effect\" id=\"rule-mapSalePrice-effect\">Sale price becomes price. Regular price becomes compare-at.<\/p><\/li><li class=\"rule\" data-on=\"false\" data-cc-rule=\"mapCasePack\"><label class=\"rule__label\" for=\"rule-mapCasePack\"><input class=\"switch\" type=\"checkbox\" role=\"switch\" id=\"rule-mapCasePack\" aria-describedby=\"rule-mapCasePack-effect\"\/><span class=\"rule__name\">Map the case-pack field<\/span><span class=\"rule__count\">16<span class=\"visually-hidden\"> findings<\/span><\/span><\/label><p class=\"rule__effect\" id=\"rule-mapCasePack-effect\">Unmapped custom fields are dropped. Case products forget how many units they hold.<\/p><\/li><li class=\"rule\" data-on=\"true\" data-cc-rule=\"mapBackorders\"><label class=\"rule__label\" for=\"rule-mapBackorders\"><input class=\"switch\" type=\"checkbox\" role=\"switch\" id=\"rule-mapBackorders\" aria-describedby=\"rule-mapBackorders-effect\" checked=\"\"\/><span class=\"rule__name\">Carry backorder settings<\/span><\/label><p class=\"rule__effect\" id=\"rule-mapBackorders-effect\">Backorder-allowed variants keep selling at zero stock.<\/p><\/li><li class=\"rule\" data-on=\"true\" data-cc-rule=\"respectStatus\"><label class=\"rule__label\" for=\"rule-respectStatus\"><input class=\"switch\" type=\"checkbox\" role=\"switch\" id=\"rule-respectStatus\" aria-describedby=\"rule-respectStatus-effect\" checked=\"\"\/><span class=\"rule__name\">Respect drafts and hidden items<\/span><\/label><p class=\"rule__effect\" id=\"rule-respectStatus-effect\">Drafts, private and hidden products stay off the storefront.<\/p><\/li><\/ul><\/div><p class=\"visually-hidden\" role=\"status\" aria-live=\"polite\" data-cc-gate-live>Gate BLOCK. 68 blocking, 2 to review.<\/p><\/div><\/section><\/aside><div class=\"demo__flow\"><div class=\"strip\" aria-hidden=\"true\" data-cc-field=\"strip\" data-area-per-mark=\"300\" data-lane-pitch=\"14\"><canvas class=\"strip__canvas\" aria-hidden=\"true\"><\/canvas><\/div><div class=\"step\"><p class=\"step__label label\"><span class=\"label__row\">Tool 1<\/span> \u00b7 Diagnose<\/p><h3 class=\"step__title\">Follow one record across the cutover<\/h3><p class=\"step__text\">SKU 00417 is a case of 24 mugs, on sale, renamed once since 2019. It arrives in the new store with the right count and, as found, three wrong fields.<\/p><section class=\"window trace\" id=\"trace\" aria-label=\"One record, from the old store to the new one\" tabindex=\"-1\" data-cc-window=\"trace\" data-cc-product=\"259\"><header class=\"window__bar\"><span class=\"window__title\" data-cc-title>record 00417<\/span><span class=\"window__status\" data-cc-status><span class=\"chip chip--mute cc-mode\" data-cc-mode=\"stale\">stale<\/span> 3 of 6 fields wrong<\/span><\/header><div class=\"window__body\"><div class=\"trace__head\"><div><h3 class=\"trace__title\">Speckled Enamel Mug 12 oz, Case of 24<\/h3><p class=\"trace__meta\">Camp Kitchen<\/p><\/div><\/div><div class=\"trace__table\" role=\"table\" aria-label=\"Field by field comparison\"><div class=\"trace__row trace__row--head\" role=\"row\"><span role=\"columnheader\">Field<\/span><span role=\"columnheader\"><a class=\"cc-head-link\" href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-admin\/post.php?post=259&#038;action=edit\" target=\"_blank\" rel=\"noopener noreferrer\">Old store<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a><\/span><span role=\"columnheader\"><a class=\"cc-head-link\" href=\"https:\/\/admin.shopify.com\/store\/execution-space-eqtpkq3j\/products\/15387591377193\" target=\"_blank\" rel=\"noopener noreferrer\">New store<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a><\/span><\/div><div class=\"trace__row\" role=\"row\" data-wrong=\"true\"><span class=\"trace__field\" role=\"rowheader\">Address<\/span><span class=\"trace__value\" role=\"cell\">\/product\/speckled-camp-mug-12oz\/<\/span><span class=\"trace__value trace__value--target\" role=\"cell\"><span class=\"trace__now\">\/products\/speckled-enamel-mug-12-oz-case-of-24<\/span><span class=\"chip chip--block\">Block<\/span><span class=\"trace__why\"><strong>renamed.<\/strong> The old address \/product\/speckled-camp-mug-12oz\/ no longer matches the new handle.<\/span><\/span><\/div><div class=\"trace__row\" role=\"row\" data-wrong=\"true\"><span class=\"trace__field\" role=\"rowheader\">SKU<\/span><span class=\"trace__value\" role=\"cell\">00417<\/span><span class=\"trace__value trace__value--target\" role=\"cell\"><span class=\"trace__now\">417<\/span><span class=\"chip chip--block\">Block<\/span><span class=\"trace__why\"><strong>changed.<\/strong> A spreadsheet read the SKU as a number. Inventory and ERP sync look the item up by this value.<\/span><\/span><\/div><div class=\"trace__row\" role=\"row\" data-wrong=\"false\"><span class=\"trace__field\" role=\"rowheader\">Price<\/span><span class=\"trace__value\" role=\"cell\">$28.80 on sale, $34.00 regular<\/span><span class=\"trace__value trace__value--target\" role=\"cell\"><span class=\"trace__now\">$28.80, compare-at $34.00<\/span><span class=\"trace__ok\">matches<\/span><\/span><\/div><div class=\"trace__row\" role=\"row\" data-wrong=\"true\"><span class=\"trace__field\" role=\"rowheader\">Case pack<\/span><span class=\"trace__value\" role=\"cell\">24 per case<\/span><span class=\"trace__value trace__value--target\" role=\"cell\"><span class=\"trace__now\">empty<\/span><span class=\"chip chip--block\">Block<\/span><span class=\"trace__why\"><strong>cleared.<\/strong> The case-pack custom field has no mapping, so the metafield arrived empty.<\/span><\/span><\/div><div class=\"trace__row\" role=\"row\" data-wrong=\"false\"><span class=\"trace__field\" role=\"rowheader\">At zero stock<\/span><span class=\"trace__value\" role=\"cell\">keeps selling<\/span><span class=\"trace__value trace__value--target\" role=\"cell\"><span class=\"trace__now\">keeps selling<\/span><span class=\"trace__ok\">matches<\/span><\/span><\/div><div class=\"trace__row\" role=\"row\" data-wrong=\"false\"><span class=\"trace__field\" role=\"rowheader\">Visibility<\/span><span class=\"trace__value\" role=\"cell\">public<\/span><span class=\"trace__value trace__value--target\" role=\"cell\"><span class=\"trace__now\">public<\/span><span class=\"trace__ok\">matches<\/span><\/span><\/div><\/div><h4 class=\"trace__next label\">Where this record is used next<\/h4><div class=\"trace__effects\"><article class=\"effect\" data-bad=\"true\"><h5 class=\"effect__title\">On the product page<\/h5><p class=\"effect__text\">The case-pack section finds no pack size and hides itself. The shopper sees $28.80 with no unit price and no pack-size line.<\/p><\/article><article class=\"effect\" data-bad=\"true\"><h5 class=\"effect__title\">At the old address<\/h5><ol class=\"effect__chain\"><li>GET \/product\/speckled-camp-mug-12oz\/<\/li><li>301 to \/products\/speckled-camp-mug-12oz<\/li><li>404. That handle does not exist.<\/li><\/ol><\/article><article class=\"effect\" data-bad=\"true\"><h5 class=\"effect__title\">In the warehouse<\/h5><p class=\"effect__text\">In this model the ERP matches stock by SKU. It asks for <code>00417<\/code>. The store holds <code>417<\/code>. No match, so stock for this item would stop syncing.<\/p><\/article><\/div><\/div><\/section><\/div><div class=\"step\"><h3 class=\"step__title\">Then every record, the same way<\/h3><p class=\"step__text\">Records are matched on the source id carried into the new store, never on SKU. A SKU that changed in transit would otherwise read as one record lost and one created.<\/p><section class=\"window ledger\" id=\"ledger\" aria-label=\"Every record that did not match\" data-cc-window=\"ledger\"><header class=\"window__bar\"><span class=\"window__title\" data-cc-title>reconcile-catalog<\/span><span class=\"window__status\" data-cc-status><span class=\"chip chip--mute cc-mode\" data-cc-mode=\"stale\">stale<\/span> 70 rows flagged<\/span><\/header><div class=\"window__body\"><p class=\"ledger__summary\">412 products matched \u00b7 1,186 variants matched \u00b7 0 lost. <strong>68 fields wrong<\/strong> on 65 products. 2 products need a person.<\/p><div class=\"filters\" role=\"group\" aria-label=\"Filter the flagged rows\"><button type=\"button\" class=\"filter\" aria-pressed=\"true\" data-cc-filter=\"all\">All <span class=\"filter__n\">70<\/span><\/button><button type=\"button\" class=\"filter\" aria-pressed=\"false\" data-cc-filter=\"handle\">Handles <span class=\"filter__n\">24<\/span><\/button><button type=\"button\" class=\"filter\" aria-pressed=\"false\" data-cc-filter=\"sku\">SKUs <span class=\"filter__n\">28<\/span><\/button><button type=\"button\" class=\"filter\" aria-pressed=\"false\" data-cc-filter=\"case_pack\">Case packs <span class=\"filter__n\">16<\/span><\/button><button type=\"button\" class=\"filter\" aria-pressed=\"false\" data-cc-filter=\"human\">Needs a person <span class=\"filter__n\">2<\/span><\/button><\/div><table class=\"table ledger__table\" role=\"table\"><caption class=\"visually-hidden\">Flagged records, most serious first<\/caption><thead role=\"rowgroup\"><tr role=\"row\"><th scope=\"col\" role=\"columnheader\">Gate<\/th><th scope=\"col\" role=\"columnheader\">Record<\/th><th scope=\"col\" role=\"columnheader\">Field<\/th><th scope=\"col\" role=\"columnheader\">Old store<\/th><th scope=\"col\" role=\"columnheader\">New store<\/th><th scope=\"col\" role=\"columnheader\"><span class=\"visually-hidden\">Details<\/span><\/th><\/tr><\/thead><tbody role=\"rowgroup\" data-cc-ledger-rows><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"handle:974:0\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Birch Camp Sock<\/span><span class=\"ledger__sku\">AP-1069-S-SND<\/span><\/th><td role=\"cell\" data-label=\"Field\">Handle<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">camp-sock-birch<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">birch-camp-sock<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-handle:974:0\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Birch Camp Sock, Handle<\/span><\/button><\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"handle:1139:0\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Birch Tin Candle<\/span><span class=\"ledger__sku\">CN-1020-08<\/span><\/th><td role=\"cell\" data-label=\"Field\">Handle<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">tin-candle-birch<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">birch-tin-candle<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-handle:1139:0\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Birch Tin Candle, Handle<\/span><\/button><\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"handle:1464:0\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Birch Trail Map<\/span><span class=\"ledger__sku\">PG-1022<\/span><\/th><td role=\"cell\" data-label=\"Field\">Handle<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">trail-map-birch<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">birch-trail-map<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-handle:1464:0\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Birch Trail Map, Handle<\/span><\/button><\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"sku:246:246\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Cedar Cast Iron Skillet<\/span><span class=\"ledger__sku\">00404<\/span><\/th><td role=\"cell\" data-label=\"Field\">SKU<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">00404<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">404<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-sku:246:246\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Cedar Cast Iron Skillet, SKU<\/span><\/button><\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"sku:262:262\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Cedar Dutch Oven<\/span><span class=\"ledger__sku\">00420<\/span><\/th><td role=\"cell\" data-label=\"Field\">SKU<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">00420<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">420<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-sku:262:262\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Cedar Dutch Oven, SKU<\/span><\/button><\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"case_pack:1445:0\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Cedar Field Notebook<\/span><span class=\"ledger__sku\">PG-1003<\/span><\/th><td role=\"cell\" data-label=\"Field\">Case pack<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">12 per case<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">empty<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-case_pack:1445:0\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Cedar Field Notebook, Case pack<\/span><\/button><\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"handle:512:0\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Cedar Flannel Shirt<\/span><span class=\"ledger__sku\">AP-1003-S-RST<\/span><\/th><td role=\"cell\" data-label=\"Field\">Handle<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">flannel-shirt-cedar<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">cedar-flannel-shirt<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-handle:512:0\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Cedar Flannel Shirt, Handle<\/span><\/button><\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-open=\"false\" data-cc-row=\"sku:254:254\"><td role=\"cell\" data-label=\"Gate\"><span class=\"chip chip--block\">Block<\/span><\/td><th scope=\"row\" role=\"rowheader\" data-label=\"Record\"><span class=\"ledger__name\">Cedar Kettle<\/span><span class=\"ledger__sku\">00412<\/span><\/th><td role=\"cell\" data-label=\"Field\">SKU<\/td><td role=\"cell\" data-label=\"Old store\" class=\"mono\">00412<\/td><td role=\"cell\" data-label=\"New store\" class=\"mono ledger__target\">412<\/td><td role=\"cell\" class=\"ledger__more\"><button type=\"button\" class=\"link-button\" aria-expanded=\"false\" aria-controls=\"why-sku:254:254\" data-cc-action=\"why\">Why<span class=\"visually-hidden\">: Cedar Kettle, SKU<\/span><\/button><\/td><\/tr><\/tbody><\/table><button type=\"button\" class=\"button button--ghost button--small ledger__toggle\" data-cc-action=\"toggle-rows\">Show all 70 rows<\/button><\/div><\/section><\/div><div class=\"step\"><p class=\"step__label label\"><span class=\"label__row\">Tool 2<\/span> \u00b7 Decide<\/p><h3 class=\"step__title\">Build the redirect map from what arrived<\/h3><p class=\"step__text\">Every product URL changes shape in this move, from \/product\/ to \/products\/. The question is where the last segment comes from.<\/p><section class=\"window map\" id=\"map\" aria-label=\"The redirect map\" data-cc-window=\"map\"><header class=\"window__bar\"><span class=\"window__title\" data-cc-title>redirects.csv<\/span><span class=\"window__status\" data-cc-status><span class=\"chip chip--mute cc-mode\" data-cc-mode=\"stale\">stale<\/span> 410 rows<\/span><\/header><div class=\"window__body\"><fieldset class=\"strategy\"><legend class=\"label\">Build the map from<\/legend><div class=\"strategy__options\"><label class=\"strategy__option\" data-checked=\"true\" data-cc-strategy=\"pattern\"><input type=\"radio\" name=\"strategy\" checked=\"\"\/><span>A URL pattern<\/span><\/label><label class=\"strategy__option\" data-checked=\"false\" data-cc-strategy=\"reconciled\"><input type=\"radio\" name=\"strategy\"\/><span>Reconciled records<\/span><\/label><\/div><p class=\"strategy__note\">Rewrites \/product\/slug\/ to \/products\/slug and assumes the handle survived.<\/p><\/fieldset><div class=\"stats\"><p class=\"stat\"><span class=\"stat__n\">410<\/span><span class=\"stat__label\">redirects in the map<\/span><\/p><p class=\"stat\" data-bad=\"true\"><span class=\"stat__n\">24<\/span><span class=\"stat__label\">point at a page that does not exist<\/span><\/p><p class=\"stat\"><span class=\"stat__n\">0<\/span><span class=\"stat__label\">chains<\/span><\/p><p class=\"stat\"><span class=\"stat__n\">0<\/span><span class=\"stat__label\">duplicate sources<\/span><\/p><\/div><p class=\"map__note\">The pattern map is right for 386 URLs and wrong for exactly the 24 products whose handle changed. A pattern cannot know which ones those are.<\/p><table class=\"table map__table\" role=\"table\"><caption class=\"visually-hidden\">A sample of the redirect map, dead destinations first<\/caption><thead role=\"rowgroup\"><tr role=\"row\"><th scope=\"col\" role=\"columnheader\">Redirect from<\/th><th scope=\"col\" role=\"columnheader\">Redirect to<\/th><th scope=\"col\" role=\"columnheader\">Lands on<\/th><\/tr><\/thead><tbody role=\"rowgroup\"><tr role=\"row\" data-severity=\"block\"><td role=\"cell\" data-label=\"Redirect from\" class=\"mono\">\/product\/steel-bottle-cedar\/<\/td><td role=\"cell\" data-label=\"Redirect to\" class=\"mono\">\/products\/steel-bottle-cedar<\/td><td role=\"cell\" data-label=\"Lands on\"><span class=\"chip chip--block\">404<\/span><\/td><\/tr><tr role=\"row\" data-severity=\"block\"><td role=\"cell\" data-label=\"Redirect from\" class=\"mono\">\/product\/canteen-ember\/<\/td><td role=\"cell\" data-label=\"Redirect to\" class=\"mono\">\/products\/canteen-ember<\/td><td role=\"cell\" data-label=\"Lands on\"><span class=\"chip chip--block\">404<\/span><\/td><\/tr><tr role=\"row\" data-severity=\"block\"><td role=\"cell\" data-label=\"Redirect from\" class=\"mono\">\/product\/kettle-ridgeline\/<\/td><td role=\"cell\" data-label=\"Redirect to\" class=\"mono\">\/products\/kettle-ridgeline<\/td><td role=\"cell\" data-label=\"Lands on\"><span class=\"chip chip--block\">404<\/span><\/td><\/tr><tr role=\"row\" data-severity=\"block\"><td role=\"cell\" data-label=\"Redirect from\" class=\"mono\">\/product\/speckled-camp-mug-12oz\/<\/td><td role=\"cell\" data-label=\"Redirect to\" class=\"mono\">\/products\/speckled-camp-mug-12oz<\/td><td role=\"cell\" data-label=\"Lands on\"><span class=\"chip chip--block\">404<\/span><\/td><\/tr><tr role=\"row\" data-severity=\"block\"><td role=\"cell\" data-label=\"Redirect from\" class=\"mono\">\/product\/cutting-board-ember\/<\/td><td role=\"cell\" data-label=\"Redirect to\" class=\"mono\">\/products\/cutting-board-ember<\/td><td role=\"cell\" data-label=\"Lands on\"><span class=\"chip chip--block\">404<\/span><\/td><\/tr><tr role=\"row\" data-severity=\"info\"><td role=\"cell\" data-label=\"Redirect from\" class=\"mono\">\/product\/ridgeline-enamel-mug\/<\/td><td role=\"cell\" data-label=\"Redirect to\" class=\"mono\">\/products\/ridgeline-enamel-mug<\/td><td role=\"cell\" data-label=\"Lands on\"><span class=\"chip chip--info\">200<\/span><\/td><\/tr><\/tbody><\/table><p class=\"fineprint\">Shopify redirects are one to one, fire only from addresses that no longer load, and cannot start from reserved paths such as \/shop or \/cart. Source: <a href=\"https:\/\/help.shopify.com\/en\/manual\/online-store\/menus-and-links\/url-redirect\" target=\"_blank\" rel=\"noopener noreferrer\">Shopify Help Center, URL redirects<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a>, read October 3, 2026.<\/p><\/div><\/section><\/div><div class=\"step\"><p class=\"step__label label\"><span class=\"label__row\">Tool 3<\/span> \u00b7 Verify<\/p><h3 class=\"step__title\">Read the first two days against the map<\/h3><p class=\"step__text\">After cutover the old addresses keep arriving. This sorts them into Covered, Dead end, Add, Reserved and Ignore, using the map above. One of them, \/shop\/, no redirect can fix, because Shopify reserves it.<\/p><section class=\"window watch\" id=\"watch\" aria-label=\"Old addresses still being requested after cutover\" tabindex=\"-1\" data-cc-window=\"watch\"><header class=\"window__bar\"><span class=\"window__title\" data-cc-title>old-urls.log<\/span><span class=\"window__status\" data-cc-status><span class=\"chip chip--mute cc-mode\" data-cc-mode=\"stale\">stale<\/span> first 48 hours, synthetic<\/span><\/header><div class=\"window__body\"><div class=\"watch__top\"><p class=\"watch__coverage\"><span class=\"watch__pct\">23%<\/span><span>of page requests reach a live page (140 of 603 hits). Bots, feeds, old image paths and reserved paths are left out.<\/span><\/p><div class=\"meter\" role=\"img\" aria-label=\"23% of page requests reach a live page, 140 of 603 hits\"><span class=\"meter__fill\" style=\"inline-size:23%\"><\/span><\/div><button type=\"button\" class=\"button button--small\" data-cc-action=\"add-suggested\">Add the 5 suggested redirects<\/button><p class=\"watch__hint\">3 requested addresses already have a redirect in the map, but it points at a missing page. Adding redirects will not fix them. Build the map from reconciled records, or keep the old handles.<\/p><\/div><table class=\"table watch__table\" role=\"table\"><caption class=\"visually-hidden\">Requested old addresses and what happens to each<\/caption><thead role=\"rowgroup\"><tr role=\"row\"><th scope=\"col\" role=\"columnheader\">Requested<\/th><th scope=\"col\" role=\"columnheader\" class=\"num\">Hits<\/th><th scope=\"col\" role=\"columnheader\">Result<\/th><th scope=\"col\" role=\"columnheader\">What to do<\/th><\/tr><\/thead><tbody role=\"rowgroup\"><tr role=\"row\" data-severity=\"mute\" data-cc-path=\"\/wp-login.php\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/wp-login.php<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">310<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--mute\">Ignore<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Bot traffic hunting for a WordPress login. Nothing to redirect.<\/td><\/tr><tr role=\"row\" data-severity=\"review\" data-cc-path=\"\/shop\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/shop\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">212<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--review\">Reserved<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Shopify reserves \/shop and will not redirect from it. Repoint menus, emails and ads at \/collections\/all.<\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-cc-path=\"\/product\/speckled-camp-mug-12oz\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product\/speckled-camp-mug-12oz\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">146<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--block\">Dead end<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">The map has this URL, but it points at a handle that does not exist.<\/td><\/tr><tr role=\"row\" data-severity=\"mute\" data-cc-path=\"\/xmlrpc.php\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/xmlrpc.php<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">122<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--mute\">Ignore<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Bot traffic. Nothing to redirect.<\/td><\/tr><tr role=\"row\" data-severity=\"review\" data-cc-path=\"\/blog\/how-to-season-cast-iron\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/blog\/how-to-season-cast-iron\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">88<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--review\">Add<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Redirect to <span class=\"mono\">\/blogs\/journal\/how-to-season-cast-iron<\/span>. Blog posts moved under \/blogs\/. The most linked article on the old site.<\/td><\/tr><tr role=\"row\" data-severity=\"info\" data-cc-path=\"\/product\/juniper-camp-tumbler\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product\/juniper-camp-tumbler\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">71<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--info\">Covered<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Covered.<\/td><\/tr><tr role=\"row\" data-severity=\"review\" data-cc-path=\"\/my-account\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/my-account\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">64<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--review\">Add<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Redirect to <span class=\"mono\">\/account<\/span>. Customer accounts moved. Passwords do not transfer, so this page also needs the reset message.<\/td><\/tr><tr role=\"row\" data-severity=\"mute\" data-cc-path=\"\/wp-content\/uploads\/2021\/05\/enamel-mug.jpg\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/wp-content\/uploads\/2021\/05\/enamel-mug.jpg<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">57<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--mute\">Ignore<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">An old image path. Fix the page that links to it, or redirect the file to its new address.<\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-cc-path=\"\/product\/trailhead-headlamp-2\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product\/trailhead-headlamp-2\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">49<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--block\">Dead end<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">The map has this URL, but it points at a handle that does not exist.<\/td><\/tr><tr role=\"row\" data-severity=\"review\" data-cc-path=\"\/spring-sale\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/spring-sale\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">41<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--review\">Add<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Redirect to <span class=\"mono\">\/collections\/sale<\/span>. A campaign link still printed on packaging inserts.<\/td><\/tr><tr role=\"row\" data-severity=\"info\" data-cc-path=\"\/product-category\/apparel\/?filter_color=navy\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product-category\/apparel\/?filter_color=navy<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">33<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--info\">Covered<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Covered by the path. The filter in the query string may not carry over, so test it.<\/td><\/tr><tr role=\"row\" data-severity=\"block\" data-cc-path=\"\/product\/steel-bottle-cedar\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product\/steel-bottle-cedar\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">29<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--block\">Dead end<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">The map has this URL, but it points at a handle that does not exist.<\/td><\/tr><tr role=\"row\" data-severity=\"review\" data-cc-path=\"\/wholesale-catalog\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/wholesale-catalog\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">27<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--review\">Add<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Redirect to <span class=\"mono\">\/collections\/wholesale<\/span>. The wholesale landing page that trade emails point to.<\/td><\/tr><tr role=\"row\" data-severity=\"info\" data-cc-path=\"\/product-category\/camp-kitchen\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product-category\/camp-kitchen\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">22<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--info\">Covered<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Covered.<\/td><\/tr><tr role=\"row\" data-severity=\"review\" data-cc-path=\"\/product-tag\/enamel\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product-tag\/enamel\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">19<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--review\">Add<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Redirect to <span class=\"mono\">\/collections\/drinkware<\/span>. No exact equivalent. Send them to the nearest collection.<\/td><\/tr><tr role=\"row\" data-severity=\"mute\" data-cc-path=\"\/feed\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/feed\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">18<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--mute\">Ignore<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">The old RSS feed. Only worth a redirect if subscribers matter.<\/td><\/tr><tr role=\"row\" data-severity=\"info\" data-cc-path=\"\/product\/harbor-quilted-throw\/\"><td role=\"cell\" data-label=\"Requested\" class=\"mono watch__path\">\/product\/harbor-quilted-throw\/<\/td><td role=\"cell\" data-label=\"Hits\" class=\"num mono\">14<\/td><td role=\"cell\" data-label=\"Result\"><span class=\"chip chip--info\">Covered<\/span><\/td><td role=\"cell\" data-label=\"What to do\" class=\"watch__note\">Covered.<\/td><\/tr><\/tbody><\/table><p class=\"visually-hidden cc-synthetic\">The requested addresses are a synthetic 17-line sample, not traffic measured on this site.<\/p><\/div><\/section><\/div><section class=\"exports\" id=\"exports\" aria-labelledby=\"exports-title\"><h3 class=\"exports__title\" id=\"exports-title\">Take the result with you<\/h3><ul class=\"exports__list\"><li class=\"export\"><div><p class=\"export__name\">redirects.csv<\/p><p class=\"export__what\" data-cc-export-what=\"redirects\">410 rows for the invented catalog, in the two columns Shopify imports: Redirect from, Redirect to. Built from the map as it stands above. Do not import it into a real store.<\/p><\/div><a class=\"button button--small\" href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/cutover\/v1\/export\/redirects.csv\" download=\"redirects.csv\" data-cc-export=\"redirects\">Download<span class=\"visually-hidden\"> redirects.csv<\/span> <svg class=\"arrow\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" style=\"transform:rotate(90deg)\"><path d=\"M2 8h10M8.5 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"square\"><\/path><\/svg><\/a><\/li><li class=\"export\"><div><p class=\"export__name\">exceptions.csv<\/p><p class=\"export__what\" data-cc-export-what=\"exceptions\">70 flagged rows with the gate, both values, the reason, the action, a Status to work down and an empty Owner column. Open it with the SKU column set to text, or use the workbook, so leading zeros stay.<\/p><\/div><a class=\"button button--small\" href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/cutover\/v1\/export\/exceptions.csv\" download=\"exceptions.csv\" data-cc-export=\"exceptions\">Download<span class=\"visually-hidden\"> exceptions.csv<\/span> <svg class=\"arrow\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" style=\"transform:rotate(90deg)\"><path d=\"M2 8h10M8.5 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"square\"><\/path><\/svg><\/a><\/li><li class=\"export\"><div><p class=\"export__name\">cutover-check-workbook.xlsx<\/p><p class=\"export__what\">Six sheets: how to use it, the as-found reconciliation, the redirect map, a gate made of formulas, the old-URL watch and the assumptions. Replace the rows with your own and the gate recounts. <span class=\"cc-example\">Static example<\/span><span class=\"visually-hidden\"> of the as-found run, not the live comparison above<\/span>.<\/p><\/div><a class=\"button button--small\" href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-content\/plugins\/cutover-check\/files\/cutover-check-workbook.xlsx\" download=\"\">Download<span class=\"visually-hidden\"> the workbook<\/span> <svg class=\"arrow\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" style=\"transform:rotate(90deg)\"><path d=\"M2 8h10M8.5 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"square\"><\/path><\/svg><\/a><\/li><\/ul><\/section><\/div><\/div><script type=\"application\/json\" data-cc-strings=\"demo\">{\"rail_aria\":\"Rules and gate\",\"rules_aria\":\"Cutover gate and migration rules\",\"rules_title\":\"migration.rules\",\"rules_status\":\"%1$s of %2$s set right\",\"gate_label\":\"Gate \u00b7 %1$s blocking \u00b7 %2$s to review\",\"gate_unverified_label\":\"Gate \u00b7 unverified\",\"gate_not_run_label\":\"Gate \u00b7 not run\",\"gate_not_run_verdict\":\"NOT RUN\",\"gate_not_run_meaning\":\"No comparison has run yet. The verdict appears here once the check has compared the two stores.\",\"gate_why\":\"Why this verdict (%s)\",\"signoff_prefix\":\"Sign off the \",\"signoff_one\":\"question\",\"signoff_many\":\"questions\",\"fix_all\":\"Fix all six\",\"reset\":\"Reset to as found\",\"finding_one\":\" finding\",\"finding_many\":\" findings\",\"rule_not_run\":\"Not compared yet.\",\"gate_live\":\"Gate %1$s. %2$s blocking, %3$s to review.\",\"gate_live_unverified\":\"Gate unverified. %s\",\"gate_live_not_run\":\"Gate not run.\",\"gate_preview_note\":\"Preview over the last fetched records; not saved. The live verdict is %1$s%2$s.\",\"gate_reason_prefix\":\"Reason: \",\"gate_stale_reason\":\"Stale: %1$s. Re-run the comparison before relying on this verdict.\",\"conn_not_run\":\"Not run. No comparison has been fetched from the two stores yet.\",\"conn_live\":\"Live \u00b7 %1$s \u00b7 last successful comparison %2$s (run #%3$s)\",\"conn_stale\":\"Stale \u00b7 %1$s \u00b7 last successful comparison %2$s (run #%3$s) \u00b7 %4$s\",\"conn_unverified\":\"Unverified \u00b7 %1$s \u00b7 fetched %2$s (run #%3$s) \u00b7 %4$s\",\"conn_token_ok\":\"Shopify connection OK\",\"conn_token_error\":\"Shopify API: %s\",\"conn_store_unknown\":\"store not configured\",\"operate\":\"Operate live\",\"reconcile_now\":\"Run the comparison now\",\"mode_preview\":\"preview \u00b7 not saved\",\"mode_live\":\"live\",\"mode_stale\":\"stale\",\"mode_unverified\":\"unverified\",\"mode_not_run\":\"not run\",\"mode_pending\":\"applying\u2026\",\"mode_applied\":\"applied %s \u00b7 re-checking\u2026\",\"mode_verified\":\"verified in run #%1$s at %2$s\",\"mode_failed\":\"failed: %s\",\"mode_locked\":\"locked: %s\",\"trace_aria\":\"One record, from the old store to the new one\",\"trace_title\":\"record %s\",\"trace_status\":\"%1$s of %2$s fields wrong\",\"trace_status_ok\":\"every field matches\",\"trace_status_missing\":\"missing on the new store\",\"trace_back\":\"Back to the mug\",\"trace_table_aria\":\"Field by field comparison\",\"col_field\":\"Field\",\"col_old\":\"Old store\",\"col_new\":\"New store\",\"trace_ok\":\"matches\",\"trace_question\":\"Needs a person.\",\"trace_next\":\"Where this record is used next\",\"effect_page\":\"On the product page\",\"effect_address\":\"At the old address\",\"effect_warehouse\":\"In the warehouse\",\"warehouse_asks\":\"In this model the ERP matches stock by SKU. It asks for \\u003Ccode\\u003E%s\\u003C\/code\\u003E.\",\"warehouse_wrong\":\"The store holds \\u003Ccode\\u003E%s\\u003C\/code\\u003E. No match, so stock for this item would stop syncing.\",\"warehouse_ok\":\"The store has it. Stock would keep syncing.\",\"trace_missing_text\":\"The new store has no record for this product. %s\",\"trace_missing_page\":\"No product page exists on the new store, so there is nothing for a shopper to find or buy.\",\"trace_missing_warehouse\":\"The ERP asks for %s and the store has no variant to answer with, so stock for this item has nothing to sync to.\",\"link_old\":\"Old store record\",\"link_new\":\"New store record\",\"link_new_variant\":\"New store variant\",\"evidence_fetched\":\"fetched %s\",\"ledger_aria\":\"Every record that did not match\",\"ledger_title\":\"reconcile-catalog\",\"ledger_status\":\"%s rows flagged\",\"ledger_summary\":\"%1$s products matched \u00b7 %2$s variants matched \u00b7 %3$s lost. \\u003Cstrong\\u003E%4$s fields wrong\\u003C\/strong\\u003E on %5$s products. %6$s products need a person.\",\"filters_aria\":\"Filter the flagged rows\",\"filter_all\":\"All\",\"ledger_caption\":\"Flagged records, most serious first\",\"col_gate\":\"Gate\",\"col_record\":\"Record\",\"col_details\":\"Details\",\"why\":\"Why\",\"close\":\"Close\",\"why_matters\":\"Why it matters.\",\"what_do\":\"What I would do.\",\"detail_explain\":\"What the two stores hold.\",\"detail_expected\":\"Expected %1$s \u00b7 actual %2$s.\",\"detail_evidence\":\"Evidence.\",\"detail_proposed\":\"Proposed correction.\",\"detail_outcome\":\"Outcome.\",\"detail_signed\":\"Signed off by %1$s at %2$s.\",\"detail_no_fix\":\"No automatic correction: a person decides.\",\"link_woo\":\"Edit in WooCommerce\",\"link_shopify\":\"Open in Shopify admin\",\"link_shopify_variant\":\"Open the variant\",\"trace_this\":\"Trace this record\",\"decided\":\"A person has decided this one\",\"ledger_empty\":\"Nothing flagged. Every mapped field on every record matches what left the old store.\",\"show_all\":\"Show all %s rows\",\"show_first\":\"Show the first %s\",\"map_aria\":\"The redirect map\",\"map_title\":\"redirects.csv\",\"map_status\":\"%s rows\",\"legend\":\"Build the map from\",\"strategy_pattern\":\"A URL pattern\",\"strategy_reconciled\":\"Reconciled records\",\"stat_total\":\"redirects in the map\",\"stat_dead\":\"point at a page that does not exist\",\"stat_chains\":\"chains\",\"stat_dups\":\"duplicate sources\",\"map_caption\":\"A sample of the redirect map, dead destinations first\",\"col_from\":\"Redirect from\",\"col_to\":\"Redirect to\",\"col_lands\":\"Lands on\",\"map_installed\":\"Installed on %3$s: %1$s of %2$s proposed redirects (checked %4$s). The table shows the proposed map.\",\"map_installed_none\":\"Nothing from this map is installed on %1$s yet (checked %2$s).\",\"map_installed_chip\":\"installed\",\"map_different_chip\":\"installed elsewhere\",\"map_tests\":\"Live path test on %1$s: %2$s (checked %3$s). Not a domain cutover test.\",\"map_fineprint\":\"Shopify redirects are one to one, fire only from addresses that no longer load, and cannot start from reserved paths such as \/shop or \/cart. Source: \\u003Ca href=\\\"https:\/\/help.shopify.com\/en\/manual\/online-store\/menus-and-links\/url-redirect\\\" target=\\\"_blank\\\" rel=\\\"noopener noreferrer\\\"\\u003EShopify Help Center, URL redirects\\u003Cspan class=\\\"visually-hidden\\\"\\u003E (opens in a new tab)\\u003C\/span\\u003E\\u003C\/a\\u003E, read October 3, 2026.\",\"watch_aria\":\"Old addresses still being requested after cutover\",\"watch_title\":\"old-urls.log\",\"watch_status\":\"first 48 hours, synthetic\",\"coverage_text\":\"of page requests reach a live page (%1$s of %2$s hits). Bots, feeds, old image paths and reserved paths are left out.\",\"meter_aria\":\"%1$s%% of page requests reach a live page, %2$s of %3$s hits\",\"add_prefix\":\"Add the \",\"add_one\":\"suggested redirect\",\"add_many\":\"suggested redirects\",\"hint\":\"%1$s requested %2$s a redirect in the map, but it points at a missing page. Adding redirects will not fix %3$s. Build the map from reconciled records, or keep the old handles.\",\"hint_one\":\"address already has\",\"hint_many\":\"addresses already have\",\"hint_it\":\"it\",\"hint_them\":\"them\",\"watch_caption\":\"Requested old addresses and what happens to each\",\"col_requested\":\"Requested\",\"col_hits\":\"Hits\",\"col_result\":\"Result\",\"col_what\":\"What to do\",\"redirect_to\":\"Redirect to \",\"status_covered\":\"Covered\",\"status_broken\":\"Dead end\",\"status_add\":\"Add\",\"status_reserved\":\"Reserved\",\"status_ignore\":\"Ignore\",\"status_installed\":\"Installed\",\"watch_synthetic\":\"The requested addresses are a synthetic 17-line sample, not traffic measured on this site.\",\"exports_title\":\"Take the result with you\",\"download\":\"Download\",\"redirects_name\":\"redirects.csv\",\"redirects_what\":\"%s rows for the invented catalog, in the two columns Shopify imports: Redirect from, Redirect to. Built from the map as it stands above. Do not import it into a real store.\",\"redirects_hidden\":\" redirects.csv\",\"exceptions_name\":\"exceptions.csv\",\"exceptions_what\":\"%s flagged rows with the gate, both values, the reason, the action, a Status to work down and an empty Owner column. Open it with the SKU column set to text, or use the workbook, so leading zeros stay.\",\"exceptions_hidden\":\" exceptions.csv\",\"workbook_name\":\"cutover-check-workbook.xlsx\",\"workbook_what\":\"Six sheets: how to use it, the as-found reconciliation, the redirect map, a gate made of formulas, the old-URL watch and the assumptions. Replace the rows with your own and the gate recounts.\",\"workbook_note\":\" of the as-found run, not the live comparison above\",\"workbook_chip\":\"Static example\",\"workbook_hidden\":\" the workbook\",\"export_preview\":\" (preview, generated in your browser)\",\"export_not_run\":\" (no run yet)\",\"rules\":{\"keepHandles\":{\"name\":\"Keep old handles\",\"on\":\"Old slugs become Shopify handles, so product URLs keep their last segment.\",\"off\":\"Handles are rebuilt from titles. Renamed products get a new URL.\"},\"skuAsText\":{\"name\":\"Import SKUs as text\",\"on\":\"SKUs arrive exactly as the ERP knows them.\",\"off\":\"The file passes through a spreadsheet. Numeric SKUs lose leading zeros and long ones turn into scientific notation.\"},\"mapSalePrice\":{\"name\":\"Map sale and regular price\",\"on\":\"Sale price becomes price. Regular price becomes compare-at.\",\"off\":\"Only the regular price is imported. Items on sale go back to full price.\"},\"mapCasePack\":{\"name\":\"Map the case-pack field\",\"on\":\"The custom field becomes the metafield custom.case_pack.\",\"off\":\"Unmapped custom fields are dropped. Case products forget how many units they hold.\"},\"mapBackorders\":{\"name\":\"Carry backorder settings\",\"on\":\"Backorder-allowed variants keep selling at zero stock.\",\"off\":\"Every variant is set to deny. Backorder items show as sold out.\"},\"respectStatus\":{\"name\":\"Respect drafts and hidden items\",\"on\":\"Drafts, private and hidden products stay off the storefront.\",\"off\":\"Everything is published. Unreleased products go live.\"}},\"featuredSku\":\"00417\",\"workbookUrl\":\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-content\/plugins\/cutover-check\/files\/cutover-check-workbook.xlsx\",\"page\":8}<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-64a7f94 cc elementor-widget elementor-widget-cc-gate-bar\" data-id=\"64a7f94\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-gate-bar.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<a class=\"gatebar\" href=\"#rules\"><span class=\"label\">Gate<\/span><span class=\"chip chip--verdict chip--mute\" data-cc-gate=\"chip\" data-cc-count=\"none\" data-cc-stale=\"1\">BLOCK<\/span><span class=\"gatebar__text\" data-cc-gate=\"text\">stale \u00b7 re-run the comparison<\/span><span class=\"gatebar__go\"><span class=\"visually-hidden\">Go to the <\/span>Rules<\/span><\/a>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t<section class=\"elementor-element elementor-element-d1e0c97 e-con-full cc decision e-flex e-con e-parent\" data-id=\"d1e0c97\" data-element_type=\"container\" data-e-type=\"container\" id=\"decision\">\n\t\t<div class=\"elementor-element elementor-element-159cbd1 e-con-full cc wrap e-flex e-con e-child\" data-id=\"159cbd1\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9566b36 cc elementor-widget elementor-widget-cc-section-head\" data-id=\"9566b36\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-section-head.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<header class=\"section-head\"><p class=\"label\"><span class=\"label__row\">Row 003<\/span><span aria-hidden=\"true\"> \u00b7 <\/span>One decision, argued<\/p><h2 class=\"section-head__title\"><span id=\"decision-title\">Keep the old handles, or clean them up?<\/span><\/h2><div class=\"section-head__intro\"><p>Every tool above depends on this one choice. Each option costs something, so here is how I would decide it for this catalog and what would change my mind.<\/p><\/div><\/header>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8334356 cc elementor-widget elementor-widget-cc-decision\" data-id=\"8334356\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-decision.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"options\"><article class=\"option\" data-current=\"false\"><p class=\"label option__tag\">Option A<\/p><h3 class=\"option__title\">Keep every old handle<\/h3><p>Old slugs become handles as they are. All 402 product URLs still need a redirect, because \/product\/ becomes \/products\/, but the last segment never changes.<\/p><dl class=\"option__facts\"><div><dt>Cost<\/dt><dd>24 products keep an address that no longer matches their name. The mug stays <span class=\"mono\">speckled-camp-mug-12oz<\/span>.<\/dd><\/div><div><dt>Risk<\/dt><dd>Lowest. A pattern-built map and a reconciled map give the same answer.<\/dd><\/div><\/dl><\/article><article class=\"option\" data-current=\"true\"><p class=\"label option__tag\">Option B<\/p><h3 class=\"option__title\">Rebuild handles from titles<\/h3><p>Clean, consistent addresses that match what the product is called today. 24 of 402 live products change their last segment.<\/p><dl class=\"option__facts\"><div><dt>Cost<\/dt><dd>The redirect map becomes the only thing between those 24 pages and a 404.<\/dd><\/div><div><dt>Risk<\/dt><dd>Highest if the map is built from a pattern: it fails on exactly those 24. Safe only when the map is built from reconciled records.<\/dd><\/div><\/dl><\/article><article class=\"option\"><p class=\"label option__tag\">Option C<\/p><h3 class=\"option__title\">Keep now, rename later in small batches<\/h3><p>Cut over with old handles. Once rankings and 404s have settled, rename the handful that matter, a few at a time, each with its redirect.<\/p><dl class=\"option__facts\"><div><dt>Cost<\/dt><dd>Two passes instead of one, and untidy addresses for a few weeks.<\/dd><\/div><div><dt>Risk<\/dt><dd>Low. If a rename hurts a page, the cause is one known change.<\/dd><\/div><\/dl><\/article><\/div><div class=\"call\"><div class=\"call__main\"><p class=\"label\">My call<\/p><p class=\"call__text\">Option A at cutover, then Option C. A replatform already changes the platform, the theme, the URLs and the page speed at once. If traffic drops afterwards, I want one fewer thing to suspect, and keeping handles gives me that. The cost is some untidy addresses.<\/p><\/div><div class=\"call__side\"><p class=\"label\">What would change it<\/p><ul class=\"call__list\"><li>Search Console showing the renamed pages earn almost no organic traffic. Then clean them now.<\/li><li>A rebrand where the old names must be gone on launch day.<\/li><li>An ERP or product feed that matches products by handle. Then renaming a handle changes data as well as the URL.<\/li><\/ul><\/div><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t<section class=\"elementor-element elementor-element-e6ad63f e-con-full cc code e-flex e-con e-parent\" data-id=\"e6ad63f\" data-element_type=\"container\" data-e-type=\"container\" id=\"code\">\n\t\t<div class=\"elementor-element elementor-element-8db893d e-con-full cc wrap e-flex e-con e-child\" data-id=\"8db893d\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c7dc264 cc elementor-widget elementor-widget-cc-section-head\" data-id=\"c7dc264\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-section-head.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<header class=\"section-head\"><p class=\"label\"><span class=\"label__row\">Row 004<\/span><span aria-hidden=\"true\"> \u00b7 <\/span>Four files<\/p><h2 class=\"section-head__title\"><span id=\"code-title\">The Shopify side, written for a real store<\/span><\/h2><div class=\"section-head__intro\"><p>The demonstration runs on synthetic data in your browser. These four files are the Shopify half of the same check on a client\u2019s catalog. None has run against a live store yet. Each says how far it has been checked.<\/p><\/div><\/header>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-53fb18b cc elementor-widget elementor-widget-cc-code-tabs\" data-id=\"53fb18b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-code-tabs.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"codebox\" data-copy-label=\"Copy\" data-copied-label=\"Copied\" data-failed-label=\"Copy failed\" data-live-copied=\"Copied %s\" data-live-failed=\"Copy failed. The text is selected.\"><div class=\"codebox__tabs\" role=\"tablist\" aria-label=\"Files\"><button type=\"button\" role=\"tab\" id=\"cc-code-53fb18b-tab-export\" aria-selected=\"true\" aria-controls=\"cc-code-53fb18b-panel-export\" tabindex=\"0\" class=\"codebox__tab\">Export the target<\/button><button type=\"button\" role=\"tab\" id=\"cc-code-53fb18b-tab-query\" aria-selected=\"false\" aria-controls=\"cc-code-53fb18b-panel-query\" tabindex=\"-1\" class=\"codebox__tab\">The bulk query<\/button><button type=\"button\" role=\"tab\" id=\"cc-code-53fb18b-tab-redirects\" aria-selected=\"false\" aria-controls=\"cc-code-53fb18b-panel-redirects\" tabindex=\"-1\" class=\"codebox__tab\">Load the redirects<\/button><button type=\"button\" role=\"tab\" id=\"cc-code-53fb18b-tab-liquid\" aria-selected=\"false\" aria-controls=\"cc-code-53fb18b-panel-liquid\" tabindex=\"-1\" class=\"codebox__tab\">The theme side<\/button><\/div><div class=\"codebox__panel\" role=\"tabpanel\" id=\"cc-code-53fb18b-panel-export\" aria-labelledby=\"cc-code-53fb18b-tab-export\"><div class=\"codebox__meta\"><div><p class=\"codebox__path\">shopify\/scripts\/export-catalog.mjs<\/p><p class=\"codebox__what\">A Node script that pulls every product and variant with one bulk operation and writes the target side of the reconciliation.<\/p><p class=\"codebox__checked\">Queries validate against the Admin GraphQL schema. Not yet run against a store.<\/p><\/div><button type=\"button\" class=\"button button--ghost button--small codebox__copy\">Copy<\/button><\/div><pre class=\"codebox__pre\" tabindex=\"0\" role=\"region\" aria-label=\"Source of shopify\/scripts\/export-catalog.mjs\"><code>#!\/usr\/bin\/env node\n\/**\n * Exports the catalog from a Shopify store as the &quot;target&quot; side of the\n * reconciliation: one JSON array of products with their variants.\n *\n *   SHOP=example.myshopify.com SHOPIFY_ADMIN_TOKEN=shpat_... node export-catalog.mjs &gt; target.json\n *\n * Needs Node 20 or later and an Admin API access token for an app with the\n * read_products scope, installed on the store: a Dev Dashboard app using the\n * client credentials grant (stores in the app&#039;s own organization only; the\n * token lasts 24 hours), or the shpat_ token of a custom app already created in\n * the store&#039;s admin. Shopify no longer allows new admin-created custom apps.\n *\n * Uses a bulk operation, so it is one request to start and one file to read,\n * whatever the size of the catalog.\n *\n * Status: every query here validates against the Admin GraphQL schema\n * (checked October 4, 2026). The script has not yet been run against a store.\n *\/\nimport { readFile } from &#039;node:fs\/promises&#039;;\nimport { createInterface } from &#039;node:readline&#039;;\nimport { Readable } from &#039;node:stream&#039;;\n\nconst { SHOP, SHOPIFY_ADMIN_TOKEN, SHOPIFY_API_VERSION = &#039;2026-07&#039; } = process.env;\nif (!SHOP || !SHOPIFY_ADMIN_TOKEN) {\n  console.error(&#039;Set SHOP and SHOPIFY_ADMIN_TOKEN.&#039;);\n  process.exit(1);\n}\n\nconst endpoint = `https:\/\/${SHOP}\/admin\/api\/${SHOPIFY_API_VERSION}\/graphql.json`;\n\nasync function admin(query, variables = {}) {\n  const response = await fetch(endpoint, {\n    method: &#039;POST&#039;,\n    headers: { &#039;Content-Type&#039;: &#039;application\/json&#039;, &#039;X-Shopify-Access-Token&#039;: SHOPIFY_ADMIN_TOKEN },\n    body: JSON.stringify({ query, variables }),\n  });\n  if (!response.ok) throw new Error(`Admin API ${response.status}: ${await response.text()}`);\n  const body = await response.json();\n  if (body.errors) throw new Error(JSON.stringify(body.errors));\n  return body.data;\n}\n\n\/\/ Strip the comment lines: the bulk operation takes the bare query.\nconst catalogQuery = (await readFile(new URL(&#039;..\/graphql\/catalog-export.graphql&#039;, import.meta.url), &#039;utf8&#039;))\n  .split(&#039;\\n&#039;)\n  .filter((line) =&gt; !line.trimStart().startsWith(&#039;#&#039;))\n  .join(&#039;\\n&#039;);\n\nconst started = await admin(\n  `mutation RunCatalogExport($query: String!) {\n    bulkOperationRunQuery(query: $query) {\n      bulkOperation { id status }\n      userErrors { field message }\n    }\n  }`,\n  { query: catalogQuery },\n);\n\nconst { bulkOperation, userErrors } = started.bulkOperationRunQuery;\nif (userErrors.length) throw new Error(JSON.stringify(userErrors));\n\n\/\/ Poll by id. bulkOperation(id:) is available from API version 2026-01, where currentBulkOperation is deprecated.\nlet operation = bulkOperation;\nwhile (operation.status === &#039;CREATED&#039; || operation.status === &#039;RUNNING&#039;) {\n  await new Promise((resolve) =&gt; setTimeout(resolve, 2000));\n  const data = await admin(\n    `query ExportStatus($id: ID!) {\n      bulkOperation(id: $id) { id status errorCode objectCount url }\n    }`,\n    { id: operation.id },\n  );\n  operation = data.bulkOperation;\n  console.error(`${operation.status} \u00b7 ${operation.objectCount} objects`);\n}\nif (operation.status !== &#039;COMPLETED&#039;) throw new Error(`Export ${operation.status}: ${operation.errorCode ?? &#039;no error code&#039;}`);\nif (!operation.url) {\n  console.log(&#039;[]&#039;);\n  process.exit(0);\n}\n\n\/**\n * How the check reads Shopify&#039;s product status. ARCHIVED counts as draft: neither has a public page.\n * UNLISTED (reachable by link, kept out of listings and search) is left out on purpose. Whether it\n * counts as public depends on the migration, so the export stops and asks instead of guessing.\n *\/\nconst STATUS = { ACTIVE: &#039;active&#039;, DRAFT: &#039;draft&#039;, ARCHIVED: &#039;draft&#039; };\n\n\/** The old store&#039;s id, from the migration.source_id metafield. Missing is allowed; garbage is not. *\/\nfunction sourceIdOf(row) {\n  if (!row.sourceId) return null;\n  const id = Number(row.sourceId.value);\n  if (!Number.isInteger(id)) throw new Error(`${row.id}: migration.source_id is &quot;${row.sourceId.value}&quot;, not a number.`);\n  return id;\n}\n\nconst toCents = (money) =&gt; (money === null ? null : Math.round(Number(money) * 100));\n\n\/\/ The file is JSONL, one object per line. Each variant names its product in __parentId.\n\/\/ It is read line by line so a large catalog never sits in memory as one string.\nconst download = await fetch(operation.url);\nif (!download.ok || !download.body) throw new Error(`Export file ${download.status}: could not download it.`);\n\nconst products = new Map();\nconst variants = [];\nfor await (const line of createInterface({ input: Readable.fromWeb(download.body), crlfDelay: Infinity })) {\n  if (!line.trim()) continue;\n  const row = JSON.parse(line);\n  if (row.__parentId) {\n    variants.push(row);\n    continue;\n  }\n  const status = STATUS[row.status];\n  if (!status) throw new Error(`${row.id}: product status ${row.status} is not mapped. Decide how the check should treat it, then add it to STATUS.`);\n  products.set(row.id, {\n    sourceId: sourceIdOf(row),\n    handle: row.handle,\n    title: row.title,\n    status,\n    casePack: row.casePack ? Number(row.casePack.value) : null,\n    variants: [],\n  });\n}\n\nfor (const row of variants) {\n  const product = products.get(row.__parentId);\n  if (!product) throw new Error(`${row.id}: variant with no product in the export (parent ${row.__parentId}).`);\n  product.variants.push({\n    sourceId: sourceIdOf(row),\n    sku: row.sku ?? &#039;&#039;,\n    options: row.selectedOptions.map((option) =&gt; [option.name, option.value]),\n    price: toCents(row.price),\n    compareAtPrice: toCents(row.compareAtPrice),\n    inventoryPolicy: row.inventoryPolicy === &#039;CONTINUE&#039; ? &#039;continue&#039; : &#039;deny&#039;,\n  });\n}\n\nconsole.log(JSON.stringify([...products.values()], null, 2));\n<\/code><\/pre><\/div><div class=\"codebox__panel\" role=\"tabpanel\" id=\"cc-code-53fb18b-panel-query\" aria-labelledby=\"cc-code-53fb18b-tab-query\" hidden><div class=\"codebox__meta\"><div><p class=\"codebox__path\">shopify\/graphql\/catalog-export.graphql<\/p><p class=\"codebox__what\">The fields the check compares, including the source id carried in a metafield so records match when a SKU or handle has changed.<\/p><p class=\"codebox__checked\">Validates against the Admin GraphQL schema. Scope: read_products.<\/p><\/div><button type=\"button\" class=\"button button--ghost button--small codebox__copy\">Copy<\/button><\/div><pre class=\"codebox__pre\" tabindex=\"0\" role=\"region\" aria-label=\"Source of shopify\/graphql\/catalog-export.graphql\"><code># The catalog as the new store holds it, in the shape the reconciliation needs.\n# Run as a bulk operation: Shopify strips the pagination arguments and returns\n# every product and variant as one JSONL file.\n#\n# migration.source_id is a metafield written during import. It carries the old\n# store&#039;s id so records can be matched even when a SKU or handle has changed.\n#\n# Scope: read_products\n{\n  products {\n    edges {\n      node {\n        id\n        handle\n        title\n        status\n        sourceId: metafield(namespace: &quot;migration&quot;, key: &quot;source_id&quot;) {\n          value\n        }\n        casePack: metafield(namespace: &quot;custom&quot;, key: &quot;case_pack&quot;) {\n          value\n        }\n        variants {\n          edges {\n            node {\n              id\n              sku\n              price\n              compareAtPrice\n              inventoryPolicy\n              selectedOptions {\n                name\n                value\n              }\n              sourceId: metafield(namespace: &quot;migration&quot;, key: &quot;source_id&quot;) {\n                value\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n<\/code><\/pre><\/div><div class=\"codebox__panel\" role=\"tabpanel\" id=\"cc-code-53fb18b-panel-redirects\" aria-labelledby=\"cc-code-53fb18b-tab-redirects\" hidden><div class=\"codebox__meta\"><div><p class=\"codebox__path\">shopify\/graphql\/redirect-import.graphql<\/p><p class=\"codebox__what\">Three mutations that stage redirects.csv and create the import, so you can read how many rows the file holds and preview the first few before anything is applied, then a query that reads the result.<\/p><p class=\"codebox__checked\">Validates against the Admin GraphQL schema. Not yet run against a store. Scopes: read and write online store navigation.<\/p><\/div><button type=\"button\" class=\"button button--ghost button--small codebox__copy\">Copy<\/button><\/div><pre class=\"codebox__pre\" tabindex=\"0\" role=\"region\" aria-label=\"Source of shopify\/graphql\/redirect-import.graphql\"><code># Loads the reconciled redirect map (redirects.csv, columns &quot;Redirect from&quot; and\n# &quot;Redirect to&quot;) through the Admin API instead of the admin screen.\n#\n# Three mutations and a status query, in order:\n#   1. StageRedirectCsv      reserve an upload target, then POST the CSV to it\n#   2. CreateRedirectImport  point Shopify at the uploaded file; read how many rows\n#                            the file holds and preview the first few, before\n#                            anything is applied\n#   3. SubmitRedirectImport  apply it, once the row count matches the map\n#   4. RedirectImportStatus  poll until finished, then read created, updated and\n#                            failed counts\n#\n# Variables for StageRedirectCsv:\n#   {&quot;input&quot;:[{&quot;resource&quot;:&quot;URL_REDIRECT_IMPORT&quot;,&quot;filename&quot;:&quot;redirects.csv&quot;,\n#              &quot;mimeType&quot;:&quot;text\/csv&quot;,&quot;httpMethod&quot;:&quot;POST&quot;}]}\n#\n# Not yet confirmed against a store: whether CreateRedirectImport wants the staged\n# target&#039;s url or its resourceUrl. I would try resourceUrl first.\n#\n# Scopes: write_online_store_navigation, read_online_store_navigation\n\nmutation StageRedirectCsv($input: [StagedUploadInput!]!) {\n  stagedUploadsCreate(input: $input) {\n    stagedTargets {\n      url\n      resourceUrl\n      parameters {\n        name\n        value\n      }\n    }\n    userErrors {\n      field\n      message\n    }\n  }\n}\n\nmutation CreateRedirectImport($url: URL!) {\n  urlRedirectImportCreate(url: $url) {\n    urlRedirectImport {\n      id\n      count\n      previewRedirects {\n        path\n        target\n      }\n    }\n    userErrors {\n      field\n      message\n    }\n  }\n}\n\nmutation SubmitRedirectImport($id: ID!) {\n  urlRedirectImportSubmit(id: $id) {\n    job {\n      id\n      done\n    }\n    userErrors {\n      field\n      message\n    }\n  }\n}\n\nquery RedirectImportStatus($id: ID!) {\n  urlRedirectImport(id: $id) {\n    id\n    count\n    createdCount\n    updatedCount\n    failedCount\n    finished\n    finishedAt\n  }\n}\n<\/code><\/pre><\/div><div class=\"codebox__panel\" role=\"tabpanel\" id=\"cc-code-53fb18b-panel-liquid\" aria-labelledby=\"cc-code-53fb18b-tab-liquid\" hidden><div class=\"codebox__meta\"><div><p class=\"codebox__path\">shopify\/sections\/case-pack-price.liquid<\/p><p class=\"codebox__what\">The section that shows case price, units per case and unit price. It hides itself when the pack size is missing, so an empty pack size never becomes a wrong unit price.<\/p><p class=\"codebox__checked\">Theme Check 3.30, recommended rules: 0 offenses. Not yet previewed in a theme.<\/p><\/div><button type=\"button\" class=\"button button--ghost button--small codebox__copy\">Copy<\/button><\/div><pre class=\"codebox__pre\" tabindex=\"0\" role=\"region\" aria-label=\"Source of shopify\/sections\/case-pack-price.liquid\"><code>{% comment %}\n  Case-pack price\n\n  Shows the case price, how many units a case holds and the price per unit.\n  The pack size is read from the product metafield custom.case_pack (integer).\n\n  If the metafield is missing or 1, the section renders nothing on the\n  storefront. A product whose pack size was lost in a migration then shows a\n  normal price instead of a wrong unit price, and the theme editor says why the\n  section is hidden.\n\n  The login setting hides only this section&#039;s prices. Real wholesale price\n  gating also needs the theme&#039;s other price outputs hidden: the product form,\n  product cards, search results and the cart.\n\n  The price shown is for the selected or first available variant. Case products\n  normally have one variant. For multi-variant products, re-render this section\n  from the theme&#039;s variant-change handler.\n{% endcomment %}\n\n{%- liquid\n  assign variant = product.selected_or_first_available_variant\n  assign case_pack = product.metafields.custom.case_pack.value | default: 0 | plus: 0\n  assign gated = false\n  if section.settings.require_login and customer == blank\n    assign gated = true\n  endif\n-%}\n\n{%- if case_pack &gt; 1 -%}\n  &lt;div class=&quot;case-pack&quot;&gt;\n    {%- if section.settings.heading != blank -%}\n      &lt;h2 class=&quot;case-pack__heading&quot;&gt;{{ section.settings.heading | escape }}&lt;\/h2&gt;\n    {%- endif -%}\n\n    {%- if gated -%}\n      &lt;p class=&quot;case-pack__gate&quot;&gt;\n        {{ section.settings.login_text | escape }}\n        &lt;a href=&quot;{{ routes.account_login_url }}&quot;&gt;{{ section.settings.login_link_label | escape }}&lt;\/a&gt;\n      &lt;\/p&gt;\n    {%- else -%}\n      &lt;dl class=&quot;case-pack__list&quot;&gt;\n        &lt;div class=&quot;case-pack__row&quot;&gt;\n          &lt;dt&gt;{{ section.settings.case_label | escape }}&lt;\/dt&gt;\n          &lt;dd&gt;{{ variant.price | money }}&lt;\/dd&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;case-pack__row&quot;&gt;\n          &lt;dt&gt;{{ section.settings.units_label | escape }}&lt;\/dt&gt;\n          &lt;dd&gt;{{ case_pack }}&lt;\/dd&gt;\n        &lt;\/div&gt;\n        {%- if section.settings.show_unit_price -%}\n          &lt;div class=&quot;case-pack__row&quot;&gt;\n            &lt;dt&gt;{{ section.settings.unit_label | escape }}&lt;\/dt&gt;\n            &lt;dd&gt;{{ variant.price | times: 1.0 | divided_by: case_pack | round | money }}&lt;\/dd&gt;\n          &lt;\/div&gt;\n        {%- endif -%}\n      &lt;\/dl&gt;\n    {%- endif -%}\n  &lt;\/div&gt;\n{%- elsif request.design_mode -%}\n  &lt;p class=&quot;case-pack__empty&quot;&gt;\n    This product has no pack size above 1 in custom.case_pack, so this section is hidden on the storefront.\n  &lt;\/p&gt;\n{%- endif -%}\n\n{% stylesheet %}\n  .case-pack__heading {\n    margin: 0 0 0.5rem;\n    font-size: 1rem;\n  }\n\n  .case-pack__list {\n    margin: 0;\n    display: grid;\n    gap: 0.25rem;\n  }\n\n  .case-pack__row {\n    display: flex;\n    justify-content: space-between;\n    gap: 1rem;\n  }\n\n  .case-pack__row dd {\n    margin: 0;\n    font-variant-numeric: tabular-nums;\n  }\n{% endstylesheet %}\n\n{% schema %}\n{\n  &quot;name&quot;: &quot;Case-pack price&quot;,\n  &quot;tag&quot;: &quot;section&quot;,\n  &quot;class&quot;: &quot;section-case-pack&quot;,\n  &quot;enabled_on&quot;: {\n    &quot;templates&quot;: [&quot;product&quot;]\n  },\n  &quot;settings&quot;: [\n    {\n      &quot;type&quot;: &quot;text&quot;,\n      &quot;id&quot;: &quot;heading&quot;,\n      &quot;label&quot;: &quot;Heading&quot;,\n      &quot;default&quot;: &quot;Sold by the case&quot;\n    },\n    {\n      &quot;type&quot;: &quot;text&quot;,\n      &quot;id&quot;: &quot;case_label&quot;,\n      &quot;label&quot;: &quot;Case price label&quot;,\n      &quot;default&quot;: &quot;Price per case&quot;\n    },\n    {\n      &quot;type&quot;: &quot;text&quot;,\n      &quot;id&quot;: &quot;units_label&quot;,\n      &quot;label&quot;: &quot;Units label&quot;,\n      &quot;default&quot;: &quot;Units per case&quot;\n    },\n    {\n      &quot;type&quot;: &quot;checkbox&quot;,\n      &quot;id&quot;: &quot;show_unit_price&quot;,\n      &quot;label&quot;: &quot;Show price per unit&quot;,\n      &quot;default&quot;: true\n    },\n    {\n      &quot;type&quot;: &quot;text&quot;,\n      &quot;id&quot;: &quot;unit_label&quot;,\n      &quot;label&quot;: &quot;Unit price label&quot;,\n      &quot;default&quot;: &quot;Price per unit&quot;\n    },\n    {\n      &quot;type&quot;: &quot;checkbox&quot;,\n      &quot;id&quot;: &quot;require_login&quot;,\n      &quot;label&quot;: &quot;Hide this section\u2019s prices from logged-out visitors&quot;,\n      &quot;default&quot;: false\n    },\n    {\n      &quot;type&quot;: &quot;text&quot;,\n      &quot;id&quot;: &quot;login_text&quot;,\n      &quot;label&quot;: &quot;Message for logged-out visitors&quot;,\n      &quot;default&quot;: &quot;Case pricing is available to trade accounts.&quot;\n    },\n    {\n      &quot;type&quot;: &quot;text&quot;,\n      &quot;id&quot;: &quot;login_link_label&quot;,\n      &quot;label&quot;: &quot;Login link label&quot;,\n      &quot;default&quot;: &quot;Log in&quot;\n    }\n  ],\n  &quot;presets&quot;: [\n    {\n      &quot;name&quot;: &quot;Case-pack price&quot;\n    }\n  ]\n}\n{% endschema %}\n<\/code><\/pre><\/div><p class=\"visually-hidden\" role=\"status\" aria-live=\"polite\"><\/p><\/div><div class=\"code__foot\"><a class=\"button\" href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-content\/plugins\/cutover-check\/files\/cutover-check-shopify-code.zip\" download>Download the four files <svg class=\"arrow\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" style=\"transform:rotate(90deg)\"><path d=\"M2 8h10M8.5 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"square\"><\/path><\/svg><\/a><p class=\"fineprint\">Validated October 4, 2026. Bulk operation status is read with bulkOperation(id:), available from API version 2026-01, where currentBulkOperation is deprecated (<a href=\"https:\/\/shopify.dev\/docs\/apps\/build\/apis\/graphql-admin\/bulk-operations\/queries\" target=\"_blank\" rel=\"noopener noreferrer\">Shopify dev docs<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a>).<\/p><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t<section class=\"elementor-element elementor-element-3d8bd3b e-con-full cc method e-flex e-con e-parent\" data-id=\"3d8bd3b\" data-element_type=\"container\" data-e-type=\"container\" id=\"method\">\n\t\t<div class=\"elementor-element elementor-element-2415ec9 e-con-full cc wrap e-flex e-con e-child\" data-id=\"2415ec9\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-38a4eed cc elementor-widget elementor-widget-cc-section-head\" data-id=\"38a4eed\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-section-head.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<header class=\"section-head\"><p class=\"label\"><span class=\"label__row\">Row 005<\/span><span aria-hidden=\"true\"> \u00b7 <\/span>Method and limits<\/p><h2 class=\"section-head__title\"><span id=\"method-title\">What is known, what is modeled, what is untested<\/span><\/h2><div class=\"section-head__intro\"><p>This is an independent demonstration. It uses only Execution Space\u2019s public pages and invented data. It is not a diagnosis of Execution Space or of any client. Here is exactly what stands behind each part.<\/p><\/div><\/header>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5e5e95b e-con-full cc quad e-flex e-con e-child\" data-id=\"5e5e95b\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t<article class=\"elementor-element elementor-element-f3ea647 e-con-full cc quad__cell e-flex e-con e-child\" data-id=\"f3ea647\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ce56141 cc quad__title elementor-widget elementor-widget-heading\" data-id=\"ce56141\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Known<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e43f400 cc quad__sub elementor-widget elementor-widget-text-editor\" data-id=\"e43f400\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Public, read October 3, 2026<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8c9655d cc elementor-widget elementor-widget-text-editor\" data-id=\"8c9655d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<ul><li>Execution Space\u2019s migration process has five steps, including one-to-one redirect mapping and data validation by reconciled counts and spot-checks. <a href=\"https:\/\/executionspace.com\/commerce\/migrate\/\" target=\"_blank\" rel=\"noopener noreferrer\">Migration page<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a><\/li><li>The homepage carries a terminal widget named reconcile-orders.exe, marked as illustrative data. <a href=\"https:\/\/executionspace.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">Homepage<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a><\/li><li>Shopify redirects are one to one, work only from addresses that no longer load, cannot start from reserved paths, and are capped at 100,000 per store (20,000,000 on Plus). <a href=\"https:\/\/help.shopify.com\/en\/manual\/online-store\/menus-and-links\/url-redirect\" target=\"_blank\" rel=\"noopener noreferrer\">Shopify Help Center<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a><\/li><li>Bulk operations return JSONL and can be polled by id from API version 2026-01. <a href=\"https:\/\/shopify.dev\/docs\/apps\/build\/apis\/graphql-admin\/bulk-operations\/queries\" target=\"_blank\" rel=\"noopener noreferrer\">Shopify dev docs<span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a><\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/article>\n\t\t<article class=\"elementor-element elementor-element-a8f5aa1 e-con-full cc quad__cell e-flex e-con e-child\" data-id=\"a8f5aa1\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c87946e cc quad__title elementor-widget elementor-widget-heading\" data-id=\"c87946e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Modeled<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3c417b9 cc quad__sub elementor-widget elementor-widget-text-editor\" data-id=\"3c417b9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Synthetic, generated from a fixed seed<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-24c9a16 cc elementor-widget elementor-widget-text-editor\" data-id=\"24c9a16\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<ul><li>The catalog: 412 products and 1,186 variants for an invented store. No real merchant\u2019s data is used.<\/li><li>The proportions of renamed, case-sold and draft products are my assumptions, not measurements of any store.<\/li><li>The six rules: common ways a catalog import goes wrong. The SKU damage reproduces what a spreadsheet does to a column left as General.<\/li><li>The old-URL log: seventeen invented lines, with product paths drawn from the catalog.<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/article>\n\t\t<article class=\"elementor-element elementor-element-108f426 e-con-full cc quad__cell e-flex e-con e-child\" data-id=\"108f426\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7a0d87a cc quad__title elementor-widget elementor-widget-heading\" data-id=\"7a0d87a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Proposed<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2c8d3f8 cc quad__sub elementor-widget elementor-widget-text-editor\" data-id=\"2c8d3f8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Not measured<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e2c8fb9 cc elementor-widget elementor-widget-text-editor\" data-id=\"e2c8fb9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<ul><li>That a full row-level check before cutover leaves the spot-check with little to find.<\/li><li>That keeping handles at cutover is the safer default for this kind of catalog.<\/li><li>The first engagement described <a href=\"\/about\/#engagement\">on the About page<\/a>, including its scope and how to judge it.<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/article>\n\t\t<article class=\"elementor-element elementor-element-af47e85 e-con-full cc quad__cell e-flex e-con e-child\" data-id=\"af47e85\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-534841c cc quad__title elementor-widget elementor-widget-heading\" data-id=\"534841c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Untested<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ae11ff2 cc quad__sub elementor-widget elementor-widget-text-editor\" data-id=\"ae11ff2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<p>Would need a development store<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-01cc0cb cc elementor-widget elementor-widget-text-editor\" data-id=\"01cc0cb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t<ul><li>The export script against a live Admin API. Its queries validate against the schema and that is all.<\/li><li>The Liquid section in a theme. It passes Theme Check and has not been previewed in one.<\/li><li>How Shopify treats a trailing slash and a query string in \u201cRedirect from\u201d. Its help page says query strings may not work as expected.<\/li><li>Scale. The browser handles this catalog instantly. At 100,000 variants the same engine would run as a Node job.<\/li><\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/article>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-febfbcb cc elementor-widget elementor-widget-cc-titled-list\" data-id=\"febfbcb\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-titled-list.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"blind\"><h3 class=\"blind__title\">What this check cannot see<\/h3><ul class=\"blind__list\"><li><strong>A value that is wrong on both sides.<\/strong> It compares two stores. It does not know the truth. A price that was wrong before is faithfully wrong after.<\/li><li><strong>Meaning.<\/strong> The right SKU with the wrong photo passes every check here.<\/li><li><strong>Fields nobody mapped.<\/strong> App data, reviews and subscriptions need their own checks. Customer passwords do not transfer at all.<\/li><li><strong>Whether the theme uses the field.<\/strong> A metafield can arrive intact and never be rendered.<\/li><li><strong>Customers and orders.<\/strong> Those need admin access and a privacy conversation first.<\/li><\/ul><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t<section class=\"elementor-element elementor-element-b45c770 e-con-full cc contact e-flex e-con e-parent\" data-id=\"b45c770\" data-element_type=\"container\" data-e-type=\"container\" id=\"contact\">\n\t\t<div class=\"elementor-element elementor-element-44e22c4 e-con-full cc wrap contact__grid e-flex e-con e-child\" data-id=\"44e22c4\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2b687ae cc elementor-widget elementor-widget-cc-section-head\" data-id=\"2b687ae\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-section-head.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"contact__lead\"><p class=\"label label--ink\"><span class=\"label__row\">Row 008<\/span><span aria-hidden=\"true\"> \u00b7 <\/span>Files and contact<\/p><h2 class=\"contact__title\" id=\"contact-title\">If this is useful, twenty minutes is enough to find out.<\/h2><div class=\"contact__actions\"><a class=\"button button--ink\" href=\"mailto:hireme@mousabatarseh.com?subject=Contract%20Shopify%20developer%3A%20Cutover%20Check\">Email Mousa <svg class=\"arrow\" viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" style=\"transform:rotate(0deg)\"><path d=\"M2 8h10M8.5 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"square\"><\/path><\/svg><\/a><a class=\"contact__phone\" href=\"tel:+12488101816\">(248) 810-1816<\/a><\/div><p class=\"contact__mail\">hireme@mousabatarseh.com<\/p><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ffe88f0 cc elementor-widget elementor-widget-cc-files\" data-id=\"ffe88f0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"cc-files.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<ul class=\"files\" aria-label=\"Files\"><li><a href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-content\/plugins\/cutover-check\/files\/Mousa-Batarseh-Resume-Execution-Space.pdf\" class=\"file\"><span class=\"file__name\">R\u00e9sum\u00e9<\/span><span class=\"file__meta\">PDF, one page<\/span><\/a><\/li><li><a href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-content\/plugins\/cutover-check\/files\/Mousa-Batarseh-Cover-Letter-Execution-Space.pdf\" class=\"file\"><span class=\"file__name\">Cover letter<\/span><span class=\"file__meta\">PDF, one page<\/span><\/a><\/li><li><a href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-content\/plugins\/cutover-check\/files\/cutover-check-workbook.xlsx\" class=\"file\" download><span class=\"file__name\">Cutover check workbook<\/span><span class=\"file__meta\">XLSX, six sheets<\/span><\/a><\/li><li><a href=\"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-content\/plugins\/cutover-check\/files\/cutover-check-shopify-code.zip\" class=\"file\" download><span class=\"file__name\">Shopify code<\/span><span class=\"file__meta\">ZIP, four files and a readme<\/span><\/a><\/li><li><a href=\"https:\/\/ats.rippling.com\/executionspace\/jobs\/a9b83d69-123a-4712-8efa-54b41d29da04\" class=\"file\" target=\"_blank\" rel=\"noopener noreferrer\"><span class=\"file__name\">The posting this answers<\/span><span class=\"file__meta\">Shopify Developer, contract<\/span><span class=\"visually-hidden\"> (opens in a new tab)<\/span><\/a><\/li><\/ul>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"footnotes":""},"class_list":["post-17","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/pages\/17","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/comments?post=17"}],"version-history":[{"count":359,"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/pages\/17\/revisions"}],"predecessor-version":[{"id":4655,"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/pages\/17\/revisions\/4655"}],"wp:attachment":[{"href":"https:\/\/mousabatarseh.com\/execution-space-proposal\/wp-json\/wp\/v2\/media?parent=17"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}