This document gives new teammates a map of the theme with a focus on the custom services we ship (stringing, knotting, gripping, remix, and T-shirt printing). Keep it close when you are onboarding a new store or chasing a regression in one of the services.
- Install Node 18+ and pnpm. Log in with
shopify loginbefore running any CLI commands. - Pull the latest theme files with
pnpm run pull(wrap it with the correctSHOPIFY_FLAG_STORE=<store>.myshopify.com). - Start local development via
pnpm devornpm run dev. Both proxy toshopify theme devand honourSHOPIFY_FLAG_STORE. - Run
npx shopify theme checkbefore you ship to keep Liquid lint clean. - Format with
npx prettier --write .(the repo has@shopify/prettier-plugin-liquidconfigured). - Preact bundles are built in
reax/. Runcd reax && pnpm install && pnpm buildafter you touch anything underreax/src/**. - Preact entry filenames stay stable (
vx-tshirt-printing.js, etc.); shared dependencies use content-hashed filenames (vx-hooks.module-<hash>.js). Rebuild all entries together and include every emitted dependency in the theme release. - The frozen
assets/vx-MultiStepForm.jscomes from46f50aeb^and must retain exportsM,f, andg. Cached Stringing/Gripping entries from that revision still need the helper exports. Checking againstHEADalone misses these older callers; verify both pre-46f50aeband current entry graphs against retained assets. New implementations belong only in hashed chunks. - Deploy new hashed dependencies before updating entry bundles. Preserve earlier hashed assets and the legacy unversioned chunks: cached pages still import those exact URLs. Do not overwrite legacy chunks with a newer build or delete them during asset cleanup.
emptyOutDir: falsepreserves them locally; deployment must retain them too. A hard refresh is not a shopper-facing cache migration strategy.
- Theme assets live where Dawn expects them: global layouts in
layout/, sections insections/, reusable snippets insnippets/, translations inlocales/, and settings inconfig/. - Service UI is server-rendered by snippets in
snippets/and hydrated by the matching Preact bundles compiled intoassets/vx-*.jsfromreax/src/components/**. - All services write configuration onto
window.s3_*.assets/product-form.jsreads those flags when a shopper clicks "Add to cart" and adds the right mix of main product, service variants, and accessory SKUs in one bundle. - Shared styles for service cards live in
assets/services.css. Tweak in there rather than touching base Dawn CSS.
The theme adds gift merchandise; Shopify's native automatic Buy X Get Y discount sets its price. Gifts remain independent cart lines that shoppers can edit or remove. Editing the purchased product later does not synchronise its gifts.
Shopify setup
- Keep the existing
buy_x_get_ymetaobject fields:offer_name,trigger_collection(one collection reference), andfree_product(one product reference). Makefree_productoptional if the definition currently requires it. - Add optional
free_collectionwith type Collection, accepting one value (collection_reference). Preserve the definition's storefront read access. free_producttakes priority whenever populated, including when it is sold out. Clear it to usefree_collection; the sources are never combined. The first matching offer still wins if trigger collections overlap.- Use a dedicated manual gift collection. Its products and the trigger collection must be available to the Online Store. Empty collections add nothing. The theme checks the complete product count and omits the gift set if Liquid returns only part of it; keep gift collections at 50 products or fewer.
- Configure a matching native automatic discount: buy one qualifying product and
get one item from
free_product, or N items fromfree_collection, free. N is the collection's product count when the full set is available. Enable repeat applications per order for multiple qualifying units. Changing collection membership or skipping sold-out gifts does not update this quantity. The theme keeps successfully added gifts even if Shopify leaves them chargeable. - Align native discount dates, customer/market eligibility and combinations with the offer's storefront availability. The theme does not read discount settings or enforce free prices. A scheduled, expired or inapplicable discount can leave auto-added merchandise chargeable. Disable the metaobject entry when its offer should stop auto-adding gifts.
Addition and stock handling
The theme selects each product's first available variant and adds one of each gift
per purchased unit in that submission. Customised purchases still use their
existing quantity of one. Products with no available variant are skipped; the
remaining gifts are still added. If every gift is sold out, only the purchased
product is added. A sold-out free_product does not fall back to the collection.
Purchased products and required customisations are added and validated first; gifts are then added individually in sequential requests. Each gift is attempted once. A failed or partially fulfilled request triggers a cart read before moving on to the next gift, preserving any units successfully added. The paid purchase and failed gift requests are never resent. If the cart cannot be read, remaining attempts stop and the theme makes one read-only refresh to open the updated cart drawer or notification. Unconfirmed gifts do not show a review message or cause a redirect. If the refresh also fails, the theme leaves the current page in place without rendering stale cart sections.
The final cart sections are refreshed after gift processing. Skipped or partially added gifts produce a notice, without removing successful gifts or checking their prices. Existing cart lines remain untouched. Notices persist across page navigation for up to five minutes, or until the next add-to-cart submission.
snippets/bxgy-offers.liquid prepares the offer data, assets/bxgy.js manages gift
selection/recovery, and assets/product-form.js sequences the requests. This
covers the existing product-form flow, not bulk order controls or accelerated
checkout buttons. Run node --test tests/bxgy.test.cjs for request-flow coverage.
Before activating a collection offer, verify its metaobject and native discount
together on staging: quantities 1 and 2, stock failures, cart edits, customisations
and the final checkout prices. The metaobject definition and discount changes are
Shopify Admin setup; deploying theme files alone does not create them.
What shoppers see
Customers pick "Pro Stringing" on eligible rackets, choose a string, colour, tension, and optionally upgrade to four-knot stringing. The UI is rendered by snippets/stringing.liquid and hydrated by reax/src/components/stringing (compiled to assets/vx-stringing.js).
Toggle switches
settings.is_stringing_enabled_globallymust be true.- The product needs metafield
custom_but_hidden.studio_pro_stringing(boolean or enum). Valuestrungmakes the "factory strung" copy render. - Set
settings.stringing_collection_idto the numeric Shopify collection id that holds string SKUs (the code wraps it ingid://shopify/Collection/<id>before querying). Optional:settings.tennis_stringing_collection_idif you mirror the feature for tennis. - Enable
settings.is_knotting_enabled_globallyto surface the 2-knot vs 4-knot choice.
Shopify data to create
- Hidden service products:
- Handle
studio-pro-stringing(contains the paid service variant). - Handle
studio-four-knots-service(upsell when a customer selects four knots).
- Handle
- Collection of string products linked to
settings.stringing_collection_id. - Each string product needs:
Metafields > custom > durability | control | repulsion_power | hitting_sound | shock_absorptionwith numeric values (out of 10) used for filtering and charts.Metafield custom_but_hidden.short_description(displayed under the title).Metafield custom_but_hidden.product_tag(optional badge).- Option 1 colour values with swatches enabled so the UI pulls the hex from
optionValues[].swatch.color.
- On the racket itself, set
Metafield custom.maximum_racket_tensionso the tension picker hides options above the safe limit.
How it adds to cart
-
snippets/stringing.liquidexposes the service variant ids viawindow.s3_stringing_service_variant_idandwindow.s3_four_knots_service_variant_id. -
When the shopper submits the form,
assets/product-form.jslooks for checked inputs namedstring-variant,string-tension, andknot-configand adds:- the stringing service variant,
- the selected string variant,
- and (if four knots selected) the four-knot service variant.
The racket is added first, and each service or add-on uses Shopify's native
parent_idrelationship so the cart, checkout, and order keep the customisation grouped.
New store checklist
- Create the two hidden service products with the exact handles listed above.
- Ensure every racket that should offer stringing has the
studio_pro_stringingmetafield populated and amaximum_racket_tensionvalue. - Populate the string collection and map its numeric id into theme settings.
- Flip on the stringing and knotting checkboxes in theme settings once products, collections, and metafields are in place.
- Translate copy under
locales/en.default.json > studio.stringing.*(duplicate keys were intentional; keep them synced if you localise).
What shoppers see
Eligible rackets surface a "Gripping Service" card. Customers can keep the factory grip or pick from curated aftermarket grips. Hydration comes from reax/src/components/gripping compiled to assets/vx-gripping.js.
Toggle switches
settings.is_gripping_enabled_globallymust be true.- Product metafield
custom_but_hidden.studio_grip_servicemust be set. settings.gripping_collection_idpoints to the collection of grip SKUs.- Optional
settings.factory_grip_imageadds the marketing image for the "no grip" option.
Shopify data to create
- Hidden service product with handle
studio-gripping-service. - Collection of grip products referenced by
settings.gripping_collection_id. - Each grip product uses:
custom_but_hidden.short_descriptionfor the UI blurb.- Colour swatches on option values so the picker can render circles.
How it adds to cart
snippets/gripping.liquidemitswindow.s3_gripping_service_variant_idand a hidden input#selected-grip-variant-idthat keeps the chosen variant id and sku.assets/product-form.jschecks that hidden field and adds both the service variant and the selected grip variant as child lines of the racket.
New store checklist
- Build the grip collection, note its numeric id, and drop it into theme settings.
- Create the
studio-gripping-serviceproduct and lock it from storefront visibility. - Confirm product metafields and translations
studio.gripping.*exist.
What shoppers see
Racket, paddle, or bag products with the Remix flag render a "Personalise" line item. Clicking loads a Radix modal powered by reax/src/components/remix (assets/vx-remix.js) that overlays a 3D model with editable decals.
Toggle switches
settings.is_remix_enabled_globallymust be true.- Product metafield
custom_but_hidden.stickersmust be truthy. - The selected variant needs metafield
custom_but_hidden.rmx_3d_modelpointing to a media item or metaobject whosenamematches an assetassets/rmx-<name>.glb.
Shopify data to create
- Hidden service products:
- Handle
remixfor rackets. - Handle
paddle-remixfor pickleball paddles. - Handle
bag-remixfor products whose Shopify product type is exactlyBadminton Kitbags.
- Handle
- Variant-level metafields used to skin the configurator:
rmx_racket_frame_colorrmx_racket_grip_colorrmx_logo_colorrmx_sticker_text_color
- Optional: upload matching
mono3.ttfandSpaceMono-Regular.ttf(already in assets) if you replicate on a new store.
How it adds to cart
sections/main-product.liquidresolves the service product by product type, andsnippets/remix.liquidwrites its first variant id towindow.s3_remix_service_variant_id.- The Preact app exposes
window.s3_remix_modal_controller.openModal()so the Liquid label can open the modal. assets/product-form.jslooks for the rendered#the-stickerelement and adds the correct remix service variant with encoded sticker text.
New store checklist
- Upload the
.glbmodels toassets/with thermx-<name>.glbnaming scheme. - Populate the remix service products and hide them from the storefront.
- Configure variant metafields for colours and set the
stickersmetafield on eligible products. - Translate copy under
locales/en.default.json > studio.remix.*if you localise.
What shoppers see
Apparel flagged for personalisation shows a "Make this T-Shirt yours" option. The modal (from reax/src/components/tshirt-printing, compiled to assets/vx-tshirt-printing.js) lets customers preview up to 12 letters, periods, or spaces on a render of the product colourway. The feature-flagged upgrade adds a country and included brand logo. Its selector defaults to India and also offers Indonesia, China, Japan, Malaysia, and Denmark. Printed country names stay uppercase English while selector labels are translated.
Toggle switches
settings.is_tshirt_printing_enabled_globallymust be true.- Product metafield
custom_but_hidden.customisemust be truthy. - The optional second line requires the
theme_feature_flagmetaobject entry with handletshirt-printing-second-lineand itsenabledboolean set to true. A missing flag or missing plus service variant keeps the original one-line experience. - The independent
tshirt-printing-materialsentry in that same definition enables Standard, Gold, Hologram, Reflective and Rose Gold swatches on all printing-eligible shirts. Standard remains the default and uses the existing variant colour metafields. When materials are enabled, country/logo availability uses the selected finish's Plus service, not the Standard Plus service. The second-line flag still controls whether country/logo can be added at all.
Shopify data to create
- Hidden service product handle
t-shirt-printing. - Unlisted two-line service product with handle
t-shirt-printing-plusand variant SKUT-SHIRT-PRINTING-PLUS(SKU matching is case-insensitive). - Variant metafields to drive the preview:
tpr_t_shirt_color(hex) for the garment fill.tpr_text_color(hex) for the printed text.
- Optional texture asset
tshirt-texture.pngalready lives inassets/; swap if the illustration changes. BarlowCondensed-SemiBold.ttfsupplies the enhanced preview font (license below). Both lines have equal cap heights; the SVG composition scales to the shirt and shrinks together for long text. Its dimensions are illustrative, not physical print measurements.- Supply transparent
lining-logo.svgandhndrd-logo.svgassets.window.s3_brandresolvesStudioto logo identifierliningandHundredtohndrd. The included logo appears beneath the country in the same color as the text. Unknown brands or failed logo loads block applying the upgrade, while name-only printing remains usable.
How it adds to cart
snippets/tshirt-printing.liquidsetswindow.s3_tshirt_printing_service_variant_idand loads the module lazily.- The Preact modal sets
#the-tshirt-textand, when applied,#the-tshirt-second-linewith the applied logo identifier indata-logo-id.assets/product-form.jsadds either the original service ort-shirt-printing-plus, never both. The uppercase country is sent as_tshirtSecondLine, with_tshirtLogoset toliningorhndrd; both use_textColor. Name-only items omit the country and logo properties. - The country cross removes the country/logo draft and preserves the name. Apply commits the new package; discard restores the previous package. The bottom Remove button clears all printing. Re-adding the country defaults to India.
- Complete desktop/mobile visual checks with both actual logos before rollout.
Optional print finishes
- Resolve these eight additional product handles and their matching variant SKUs (case-insensitive):
t-shirt-printing-gold,t-shirt-printing-hologram,t-shirt-printing-reflective,t-shirt-printing-rose-gold, and the correspondingt-shirt-printing-plus-gold,t-shirt-printing-plus-hologram,t-shirt-printing-plus-reflective,t-shirt-printing-plus-rose-gold. They must be resolvable by the Online Store theme and purchasable. A missing, mismatched or unavailable variant disables only that finish/package; it never substitutes Standard. - Reuse the existing
theme_feature_flagdefinition (display name plus boolean fieldenabled, storefront access enabled). Create an Active entry named "T-shirt printing materials", handletshirt-printing-materials, initially with Enabled = false. Liquid readsmetaobjects.theme_feature_flag['tshirt-printing-materials'].enabled.value; a missing entry or false value preserves the current flow. - The selected finish applies to name, country and included logo together. Static gradients approximate the supplied material samples; they do not promise physical reflective or holographic behaviour. Existing print dimensions, country list and logo mapping are unchanged.
- Swatches show the selected package's total service price, read from Shopify. The country/logo link shows that finish's Plus-minus-single difference. Non-positive prices or upgrade differences display the translated FREE label; no MRP or fixed rupee amount drives the charge.
- Exactly one printing service variant is added, replacing the Standard service. Material-aware selections include
_tshirtMaterial(standard,gold,hologram,reflective,rose-gold). Standard keeps the existing_textColor; specialty orders useGOLD,HOLOGRAM,REFLECTIVE, orROSE GOLD. Name, country, logo, SKU, native parent relationship and rollback contracts are retained. Legacy modals omit_tshirtMaterialand still resolve Standard. - Finish changes are drafts until Apply; Discard restores the committed finish. Removing country/logo retains the finish, while removing personalisation resets Standard. A package change that leaves an unavailable finish selected blocks Apply until the shopper explicitly chooses an available combination.
- The modal only exposes finishes when the current PDP's
product-formadvertisessupportsTshirtMaterials. This prevents a newly loaded modal paired with a cached old form from ordering the wrong service. Keep legacy globals and all retained Preact dependency chunks; deploy new dependencies before changed entries. - With the flag enabled, printing resolves ten unique product handles (two Standard plus eight specialty). Audit other page features against Shopify's 20-unique-handle
all_productslimit before extending this list. - Before enabling, test all ten finish/package combinations, both independent flags, missing/unavailable services, FREE price variations, Apply/Discard/Remove, shirt variant changes, cached/new scripts, mobile/desktop previews and real cart/checkout/order properties. An unpublished theme still shares Shopify product and inventory data. Turning this flag off hides the new selector; existing cart/order service lines are not rewritten.
New store checklist
- Keep the
t-shirt-printingproduct active and directly resolvable by the Online Store theme. - Create the
t-shirt-printing-plusproduct with variant SKUT-SHIRT-PRINTING-PLUS(any letter case is accepted). Set its product status to Unlisted so Liquid can resolve it without surfacing it in search or recommendations. - Make the plus variant purchasable before enabling the feature: give it inventory, continue selling when out of stock, or disable inventory tracking. The theme deliberately keeps the one-line experience when this variant is unavailable.
- Create the reusable
theme_feature_flagdefinition with a display-name field and anenabledboolean, then create thetshirt-printing-second-lineentry. Its definition type must be exactlytheme_feature_flag, its boolean field key must be exactlyenabled, and the entry must be active. Keep it disabled until the product, font, and theme code are deployed. - Populate variant metafields for every colourway.
- Confirm the product carries the
customisemetafield toggle. - Update translations under
locales/en.default.json > studio.tshirt_printing.*when localising.
-
Create all hidden service products with handles:
studio-pro-stringingstudio-four-knots-servicestudio-gripping-serviceremixpaddle-remixt-shirt-printingt-shirt-printing-plus
Keep them in the "Services" product type. Products resolved through
all_productsmust remain active and directly accessible; use Shopify's Unlisted product status when they should stay out of search and recommendations. -
Build the collections referenced by theme settings and capture their numeric ids (Shopify Admin -> open the collection -> copy the number at the end of the URL).
-
Configure theme settings under "Product Customisations" (toggle checkboxes, paste collection ids, upload optional factory grip image).
-
Create and assign product metafields:
- Booleans for
studio_pro_stringing,studio_grip_service,stickers,customise. - Numbers for
maximum_racket_tension. - Variant-level colour metafields listed above.
- String product metric metafields under
customnamespace.
- Booleans for
-
Upload supporting assets (
rmx-*.glb, optional textures) intoassets/. -
Verify translations exist in
locales/en.default.jsonfor thestudio.*namespaces. Duplicated English strings are placeholders-replace them with store-specific copy or locales. -
Run through the CX journey in
shopify theme dev: select each service, add to cart, and confirmcart.jsshows the racket as the parent with its service and add-on child lines.
- Theme snippets:
snippets/stringing.liquid,snippets/gripping.liquid,snippets/remix.liquid,snippets/tshirt-printing.liquid. - Service scripts:
assets/vx-stringing.js,assets/vx-gripping.js,assets/vx-remix.js,assets/vx-tshirt-printing.js(source inreax/src/components/**). - Cart orchestration:
assets/product-form.js. - Shared styles:
assets/services.css. - Theme settings:
config/settings_schema.json("Product Customisations" group) and defaults inconfig/settings_data.json. - Translation keys:
locales/en.default.jsonunder thestudio.*namespace buckets.
- Always rebuild the Preact bundles (
cd reax && pnpm build) before you push service changes, otherwise Shopify will still serve the previousassets/vx-*.jsfiles. - Use the browser console to inspect
window.s3_*coverage if a bundle fails-missing values usually point to misconfigured handles or metafields. - If a collection id is wrong, the Preact fetch falls back to the "empty" translation and logs to console; keep DevTools open while testing.
- Theme Check will not catch missing metafields, so keep this README's checklist handy when onboarding a new store.
Welcome aboard-reach out in #dev-themes if any of these flows diverge from what you see locally.