Replaces the hand-rolled shopifyFetch wrapper and raw query strings with
the hydrogen preview package (0.0.0-preview-116d5d7-20260730141607), the
same build pinned in hydrogen-preview/.
- services/shopify/client: createStorefrontClient({type: "public"}) with a
static request context. Two module-scoped clients — uncached for carts,
products and customers; revalidate-3600 for shop policies — using the
client's custom fetch option to carry Next's caching hints.
- graphql/*: every document wrapped in gql(), fragments composed via the
second argument instead of string interpolation, and $country/$language
declared with @inContext so hydrogen injects them.
- shopifyFetch keeps its {query, variables} call shape so call sites are
unchanged, but is now generic over the document, so data is inferred. It
re-raises GraphQL errors, which hydrogen returns rather than throws.
Env var names, the 2025-07 API version pin, and the client-side fetching
architecture are unchanged.
Turning on type coverage surfaced real defects, not just annotations:
- hydrogen gql check caught $discountCodes: [String!] used where the field
requires [String!]!.
- Cart and customer mutation payloads are nullable and were dereferenced
unconditionally, so a failed mutation threw a TypeError. Adds a shared
unwrapCartPayload helper (collapsing five copies of the same userErrors
check) and explicit null handling in the customer service, so a null
payload reads as an error rather than success with no errors.
- Search results are a Product | Page | Article union; adds __typename to
the queries and narrows on it.
- Widens nullable fields (altText, image, customer.email, totalTaxAmount)
in the domain interfaces and in the structural duplicates some
components declare locally.
Adds a typecheck script (tsc --noEmit && hydrogen gql check); it passes
with 0 errors. Two deprecation warnings are left alone as acting on them
would change behaviour: ProductOption.values and CartCost.totalTaxAmount.
Verified against mock.shop: build prerenders the policy pages through the
cached client, and in the browser the product grid, search with facets,
collection filter round-trip, add-to-cart and quantity update all work.
Customer account flows are untested — they need real credentials.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JaxgqPbFxLsSuLPZom2kdC
Shopify gives every single-variant product one option named Title with the
lone value Default Title. It isn't a real choice, so filter it out of the
product detail options, the cart line summaries, and the payload the store
assistant sees. Variant matching still reads the raw selectedOptions, so
single-SKU products keep resolving their variant.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuRVxuyMUTzWKXW8P5Tn8A
- /api/chat streams via AI SDK v7 through OpenRouter (OPENROUTER_API_KEY),
with a store-specific system prompt and five read-only tools:
searchCatalogue, getProductDetails, listCollections,
getCollectionProducts, browseProducts
- Sidebar assistant on the right that opens from a launcher and expands,
built on ai-elements (conversation, message, prompt-input, tool) with
shimmer on in-flight tool calls
- Extract services/shopify/catalog.ts so the Storefront fetchers have no
React imports and can run in a Route Handler; the client hooks now
re-export from it
- ai-elements pulled in the canonical shadcn primitives, replacing the
hand-rolled command/dialog/button variants; search dialog moved to the
cmdk-based Command and CommandDialog forwards shouldFilter
- Pin shiki to ^3.19.0 to match streamdown and drop the duplicate copy
- Add .env.example documenting the required env vars
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jWbNNJLksC1QG8z8845FX