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.
templates/page.pre-book.json creates a standalone page using sections/pre-book.liquid.
Create a Shopify page and assign the pre-book theme template. In the theme editor,
set its campaign image URL, introduction, full selling price, release information and campaign
terms. The template starts with the Shopify-hosted Hypercourt Adapt artwork URL;
replace it using the section's Campaign image URL setting. Address
collection happens later, with the balance payment. Each submission books one pair.
In the section editor, use Add block → Colourway image to add an image URL for each colourway. Use the exact value from Colourways. The template starts without image blocks so it can sync before the updated section schema. Selecting a colour updates the main image and its alt text. A missing colour image falls back to the campaign image. If that is also blank, the image is hidden until a colour with an image is selected. These blocks only map images; they do not add colours or change the options sent to the server.
The section also exposes the eyebrow, deposit explanation, form labels, contact note,
button text, next steps and payment-return messages. [amount] inserts ₹500 into
the deposit explanation, button label and next steps; editing copy does not change
the server's charge. Clearing optional copy hides it. Required field labels, button
text and payment messages fall back to the default translation when cleared.
The section starts closed. Before opening it:
- Deploy shopify-server PR #9 and configure
PRE_BOOK_PRODUCTSwith campaign IDhypercourt-adapt, amount500, the exact colour/size values shown in the editor, the Google Sheet, Shopify customer tag and KwikEngage event. The page displays a fixed ₹500 deposit; its server campaign must use that amount. The browser never supplies the amount or creates a Shopify order. - Set the section's HTTPS server URL (default
https://sitemap.hndrd.co). Allow the storefront origin in the server's CORS and return URL allowlist. Share the Sheet with the service account and enable the verified Easebuzz transaction webhook. - In a preview, enable Open pre-booking, then verify validation, a successful
payment, a cancelled/failed payment, and returning with the browser Back button.
Check the deposit and option values in Easebuzz and the Sheet, and the Shopify
customer tag and KwikEngage event. The
?prebook=successbanner is display-only; it is not evidence that the webhook has verified or recorded a payment. - Prove the later WhatsApp/email balance link, address collection, and manual Shopify order against the same booking before opening the public campaign.
The server is the authority for allowed options and price. A theme setting does
not update PRE_BOOK_PRODUCTS. The form posts contact details and options.colour
and options.size to /pre-book/initiate, then redirects only to Easebuzz's HTTPS
production or test checkout. It stores no customer details in browser storage.
Custom events remain prebook:error and prebook:redirect on the form, and
prebook:result on the document.
PR #9 writes the Sheet first; Shopify tagging and KwikEngage delivery are best-effort. Reconcile paid Easebuzz transactions with the Sheet and inspect server logs for missing tags/events. Replaying a webhook for an already saved row does not retry those notifications. Sequential webhook retries are deduplicated; concurrent deliveries can still race, so check transaction IDs before manual fulfilment.
Run node --test tests/pre-book.test.cjs for the client request-flow checks. Theme
Check and local browser mocks do not prove deployed payments or reconciliation.
- 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(complete four-knot service, replacing the base service).
- 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:- exactly one service variant:
studio-pro-stringingfor two knots, orstudio-four-knots-servicefor four knots, - and the selected string variant.
The selected service carries the racket, string, tension, and knot properties. Both knot options show their full service variant prices; applying a selection updates the service card price. The four-knot product price must cover the complete service, not just an upgrade fee. The first variant of each service supplies its ID, availability, and price.
Missing or unavailable selected services block submission before any cart request. Four knots never falls back to the base service. New modals disable four knots when the PDP has an older cached product form that still adds both services. Deploy the updated product form, Liquid, and rebuilt entry together, retaining the dependency assets described above. Existing cart lines are not migrated.
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. - exactly one service variant:
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.