Business Rules Assessment

vercel/commerce

Generated by Inkwell Forge — automated codebase documentation analysis. Based on analysis of 5 screens. Subject matter expert review is recommended before distribution.

May 5, 2026

Commerce Application

Business Rules Assessment

May 2026


Generated by Inkwell Forge — automated codebase documentation analysis. Subject matter expert review is recommended before distribution.


1. Overview

Scope

This Business Rules Assessment documents every business rule, validation, calculation, and conditional logic observable from the screen-level technical documentation of the Commerce application. Rules are extracted from five screens: Product Detail (/product/[handle]), Home (/), Search Detail (/search/[collection]), Search (/search), and Item Detail (/[page]). Business rules derived from screens not included in the assessed documentation — including the cart, checkout, account management, and any header/navigation components — are outside the scope of this document. This document reflects a partial view of the application; rules governing variant selection, add-to-cart mutations, payment processing, and order management are not covered here.

Methodology

Rules were extracted by systematically scanning each screen's documentation across all documented sections: access control (§2), UI layout and conditional rendering (§3), data flow (§4), API call response handling (§5), event handlers (§6), forms and validation (§7), explicit business logic (§8), navigation (§9), error handling (§10), and security considerations (§14). Each extracted rule is expressed declaratively — stating what must be true or what must happen — rather than describing implementation mechanics. Rules are classified using BABOK v3.0 categories (Structural, Operative, Decision), expressed in controlled natural language per SBVR v1.5 principles, and structured for decision table representation per DMN v1.5 where multiple conditions determine an outcome.

Limitations

All rules in this document are reverse-engineered from technical screen documentation written for developers; they are not derived from requirements documents, policy manuals, or stakeholder interviews. Business rationale is stated only where the documentation explicitly provides it; all other rationale fields are marked accordingly. Rules governing child components referenced in the screen documentation (e.g., ProductDescription, ThreeItemGrid, Carousel, Gallery, ProductGridItems) are not fully documented here because their internal implementations are outside the scope of the provided documentation. Where the documentation identifies a potential defect or edge case, that observation is captured in the Gap Analysis rather than as a rule.

Coverage

This Business Rules Assessment covers 5 screens of the Commerce application:

Screen Name Route
Product Detail /product/[handle]
Home /
Search Detail /search/[collection]
Search /search
Item Detail /[page]

Business rules derived from screens not included in the assessed documentation are outside the scope of this document.

Disclosure

Generated by Inkwell Forge — automated codebase documentation analysis. Subject matter expert review is recommended before distribution.

How to Use This Document


2. Rule Inventory

# Rule Name Domain Category Enforcement Criticality Screen(s)
BR-001 Hidden Product Tag Suppresses SEO Indexing Content Visibility & Access Control Operative Server High Product Detail (/product/[handle])
BR-002 Missing Product Handle Renders 404 Content Visibility & Access Control Operative Server Critical Product Detail (/product/[handle])
BR-003 Missing Collection Handle Renders 404 Content Visibility & Access Control Operative Server Critical Search Detail (/search/[collection])
BR-004 Missing Page Handle Renders 404 Content Visibility & Access Control Operative Server Critical Item Detail (/[page])
BR-005 All Storefront Screens Are Publicly Accessible Content Visibility & Access Control Structural Server Low Product Detail (/product/[handle]), Home (/), Search Detail (/search/[collection]), Search (/search), Item Detail (/[page])
BR-006 Product JSON-LD Availability Maps from availableForSale SEO & Structured Data Decision Server Medium Product Detail (/product/[handle])
BR-007 JSON-LD Uses AggregateOffer for Variant Price Range SEO & Structured Data Structural Server Medium Product Detail (/product/[handle])
BR-008 JSON-LD Price Currency Sourced from Min Variant Price SEO & Structured Data Structural Server Low Product Detail (/product/[handle])
BR-009 Product SEO Metadata Controlled by Hidden Product Tag SEO & Structured Data Decision Server High Product Detail (/product/[handle])
BR-010 Collection SEO Title Uses Fallback Chain SEO & Structured Data Decision Server Medium Search Detail (/search/[collection])
BR-011 Collection SEO Description Uses Three-Tier Fallback SEO & Structured Data Decision Server Medium Search Detail (/search/[collection])
BR-012 Item Detail SEO Title Uses Two-Tier Fallback SEO & Structured Data Decision Server Medium Item Detail (/[page])
BR-013 Item Detail SEO Description Uses Two-Tier Fallback SEO & Structured Data Decision Server Medium Item Detail (/[page])
BR-014 Item Detail OpenGraph Type Is article SEO & Structured Data Structural Server Low Item Detail (/[page])
BR-015 Item Detail OpenGraph Timestamps Sourced from Shopify SEO & Structured Data Structural Server Low Item Detail (/[page])
BR-016 Home Page OpenGraph Type Is website SEO & Structured Data Structural Server Low Home (/)
BR-017 Product Gallery Capped at Five Images Product Display & Merchandising Structural Server Medium Product Detail (/product/[handle])
BR-018 Related Products Section Suppressed When Empty Product Display & Merchandising Operative Server Low Product Detail (/product/[handle])
BR-019 Homepage Features Exactly Three Products in Hero Grid Product Display & Merchandising Structural Server Medium Home (/)
BR-020 Empty Collection Renders "No Products" Message Product Display & Merchandising Operative Server Medium Search Detail (/search/[collection])
BR-021 Empty Search Results Render "No Match" Message Product Display & Merchandising Operative Server Medium Search (/search)
BR-022 Search Result Count Uses Singular/Plural Grammar Product Display & Merchandising Operative Server Low Search (/search)
BR-023 Result Summary Suppressed When No Search Query Product Display & Merchandising Operative Server Low Search (/search)
BR-024 Product Grid Suppressed When No Results Product Display & Merchandising Operative Server Low Search (/search)
BR-025 Sort Parameter Resolves to Valid Sort Configuration Search & Sort Decision Server High Search Detail (/search/[collection]), Search (/search)
BR-026 Unrecognized Sort Parameter Falls Back to Default Search & Sort Operative Server Medium Search Detail (/search/[collection]), Search (/search)
BR-027 Related Product Tiles Link to Product Detail with Prefetch Navigation & Routing Operative Client Low Product Detail (/product/[handle])
BR-028 Item Detail Date Formatted Using Server Locale Content Rendering Operative Server Low Item Detail (/[page])
BR-029 Item Detail Page Body Rendered as Raw HTML Content Rendering Structural Server Medium Item Detail (/[page])

3. Business Rules Catalog

3.1: Content Visibility & Access Control

Description: Rules governing which content is accessible to visitors, how missing or invalid content is handled, and the public-access policy of the storefront.

Screens involved: Product Detail (/product/[handle]), Home (/), Search Detail (/search/[collection]), Search (/search), Item Detail (/[page])


BR-001: Hidden Product Tag Suppresses SEO Indexing

Domain: Content Visibility & Access Control Category: Operative Enforcement: Server Criticality: High — a failure here would expose draft or internal products to search engine indexing, potentially surfacing incomplete or incorrect product data in search results.

Rule Statement: A product tagged with HIDDEN_PRODUCT_TAG must have its robots.index, robots.follow, and googleBot.index metadata directives set to false, preventing search engine crawling and indexing of that product page.

Conditions:

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §2, §8, §14

Business Rationale: Allows merchants to publish draft, internal, or test products at live URLs without those products appearing in search engine results. This is a soft-visibility mechanism, not an access control mechanism — the URL remains publicly accessible.

Related Rules: BR-009 (SEO metadata also controlled by this flag), BR-002 (the complementary hard-visibility rule)

Testing Implications:


BR-002: Missing Product Handle Renders 404

Domain: Content Visibility & Access Control Category: Operative Enforcement: Server Criticality: Critical — without this rule, requests for non-existent product handles would produce broken or empty page renders rather than a proper HTTP 404 response.

Rule Statement: If getProduct returns a falsy value for a given handle, the application must call notFound() and render the 404 page, both during metadata generation and during page rendering.

Conditions:

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §5, §8, §9, §10

Business Rationale: Ensures that invalid or deleted product URLs return a proper 404 HTTP response rather than a broken page, which is important for both user experience and SEO (search engines should receive 404 for removed products).

Related Rules: BR-003, BR-004 (same pattern on other screens)

Testing Implications:


BR-003: Missing Collection Handle Renders 404

Domain: Content Visibility & Access Control Category: Operative Enforcement: Server Criticality: Critical — without this rule, requests for non-existent or unpublished collections would produce broken or empty page renders.

Rule Statement: If getCollection returns a falsy value for a given collection handle during metadata generation, the application must call notFound() and render the 404 page.

Conditions:

Validation Details:

Source Evidence: Search Detail (/search/[collection]) — §5, §8, §9, §10, §14

Business Rationale: Prevents access to invalid or unpublished Shopify collection handles, returning a standard 404 rather than a broken or empty page. Also prevents information leakage about the collection namespace.

Related Rules: BR-002, BR-004

Testing Implications:


BR-004: Missing Page Handle Renders 404

Domain: Content Visibility & Access Control Category: Operative Enforcement: Server Criticality: Critical — without this rule, requests for non-existent Shopify page handles would produce broken renders rather than a proper HTTP 404 response.

Rule Statement: If getPage returns a falsy value for a given page handle, the application must call notFound() and render the 404 page, both during metadata generation and during page rendering.

Conditions:

Validation Details:

Source Evidence: Item Detail (/[page]) — §2, §5, §8, §9, §10

Business Rationale: Ensures that invalid or deleted Shopify page handles return a proper 404 HTTP response rather than a broken page.

Related Rules: BR-002, BR-003

Testing Implications:


BR-005: All Storefront Screens Are Publicly Accessible

Domain: Content Visibility & Access Control Category: Structural Enforcement: Server Criticality: Low — this is an intentional design constraint, not a defect risk. Criticality is low because the rule describes the absence of access control, which is the intended behavior for a public storefront.

Rule Statement: All five assessed screens must be accessible to any visitor without authentication, session validation, or role-based authorization checks.

Conditions: None — this rule applies unconditionally to all assessed screens.

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §2; Home (/) — §2; Search Detail (/search/[collection]) — §2; Search (/search) — §2; Item Detail (/[page]) — §2

Business Rationale: These screens display public product catalog and content data. Requiring authentication would create friction for prospective customers and is not appropriate for a public-facing storefront.

Related Rules: BR-001 (soft visibility via SEO tags, not access control)

Testing Implications:


3.2: SEO & Structured Data

Description: Rules governing how search engine metadata, Open Graph tags, robots directives, and JSON-LD structured data are generated and populated for each screen.

Screens involved: Product Detail (/product/[handle]), Home (/), Search Detail (/search/[collection]), Item Detail (/[page])


BR-006: Product JSON-LD Availability Maps from availableForSale

Domain: SEO & Structured Data Category: Decision Enforcement: Server Criticality: Medium — incorrect availability data in structured markup could mislead search engines about product stock status, affecting rich result accuracy.

Rule Statement: The availability field in the product's JSON-LD structured data must be set to https://schema.org/InStock when product.availableForSale is true, and to https://schema.org/OutOfStock when product.availableForSale is false.

Conditions:

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §8

Business Rationale: Enables Google and other search engines to display accurate in-stock/out-of-stock status in product rich results, improving click-through quality.

Related Rules: BR-007, BR-008

Testing Implications:


BR-007: JSON-LD Uses AggregateOffer for Variant Price Range

Domain: SEO & Structured Data Category: Structural Enforcement: Server Criticality: Medium — using the wrong schema type would produce invalid structured data, potentially disqualifying the product from Google rich results.

Rule Statement: The product JSON-LD structured data must use the AggregateOffer schema type to represent the product's price range, populating highPrice from priceRange.maxVariantPrice.amount and lowPrice from priceRange.minVariantPrice.amount.

Conditions: None — this rule applies to all products unconditionally.

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §8

Business Rationale: AggregateOffer is the correct schema.org type for products with multiple variants at different price points. Using it enables Google to display price ranges in rich results.

Related Rules: BR-006, BR-008

Testing Implications:


BR-008: JSON-LD Price Currency Sourced from Min Variant Price

Domain: SEO & Structured Data Category: Structural Enforcement: Server Criticality: Low — incorrect currency code in structured data would affect rich result display accuracy but would not break the page or user workflow.

Rule Statement: The priceCurrency field in the product JSON-LD structured data must be sourced from priceRange.minVariantPrice.currencyCode.

Conditions: None — this rule applies unconditionally.

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §8

Business Rationale: Not stated in documentation — verify with business stakeholders.

[Not documented — WHO: Product owner or SEO specialist; WHAT: Is it intentional to source priceCurrency from the minimum variant price rather than a store-level currency setting, and what should happen if variants have different currency codes?; WHERE: Insert in the Business Rationale field of BR-008 above]

Related Rules: BR-007

Testing Implications:


BR-009: Product SEO Metadata Controlled by Hidden Product Tag

Domain: SEO & Structured Data Category: Decision Enforcement: Server Criticality: High — failure to suppress robots metadata for hidden products would allow draft or internal products to be indexed by search engines.

Rule Statement: If a product is tagged with HIDDEN_PRODUCT_TAG, the page metadata must set robots.index = false, robots.follow = false, and equivalent googleBot directives; otherwise, default indexing metadata must apply.

Conditions:

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §2, §8

Business Rationale: Allows merchants to maintain draft or internal products at live URLs without those pages appearing in search engine results.

Related Rules: BR-001

Testing Implications:


BR-010: Collection SEO Title Uses Fallback Chain

Domain: SEO & Structured Data Category: Decision Enforcement: Server Criticality: Medium — incorrect or missing SEO titles reduce search result quality but do not break functionality.

Rule Statement: The <title> tag for a collection page must use collection.seo.title if present; otherwise it must fall back to collection.title.

Conditions:

Validation Details:

Source Evidence: Search Detail (/search/[collection]) — §8

Business Rationale: Allows merchants to set SEO-optimized titles that differ from the collection's display name, following Shopify's standard SEO override pattern.

Related Rules: BR-011

Testing Implications:


BR-011: Collection SEO Description Uses Three-Tier Fallback

Domain: SEO & Structured Data Category: Decision Enforcement: Server Criticality: Medium — missing or poor meta descriptions reduce search result quality but do not break functionality.

Rule Statement: The meta description for a collection page must use collection.seo.description if present; otherwise collection.description; otherwise a generated string of the form "[collection.title] products".

Conditions:

Validation Details:

Source Evidence: Search Detail (/search/[collection]) — §8

Business Rationale: Ensures every collection page has a meaningful meta description for search engines, even when merchants have not explicitly configured SEO fields.

Related Rules: BR-010

Testing Implications:


BR-012: Item Detail SEO Title Uses Two-Tier Fallback

Domain: SEO & Structured Data Category: Decision Enforcement: Server Criticality: Medium — missing SEO titles reduce search result quality but do not break functionality.

Rule Statement: The <title> tag for an item detail page must use page.seo.title if present; otherwise it must fall back to page.title.

Conditions:

Validation Details:

Source Evidence: Item Detail (/[page]) — §8

Business Rationale: Follows Shopify's standard SEO override pattern, allowing merchants to set search-optimized titles distinct from the page's display title.

Related Rules: BR-013

Testing Implications:


BR-013: Item Detail SEO Description Uses Two-Tier Fallback

Domain: SEO & Structured Data Category: Decision Enforcement: Server Criticality: Medium — missing meta descriptions reduce search result quality but do not break functionality.

Rule Statement: The meta description for an item detail page must use page.seo.description if present; otherwise it must fall back to page.bodySummary.

Conditions:

Validation Details:

Source Evidence: Item Detail (/[page]) — §8

Business Rationale: Follows Shopify's standard SEO override pattern, allowing merchants to set search-optimized descriptions distinct from the page body summary.

Related Rules: BR-012

Testing Implications:


BR-014: Item Detail OpenGraph Type Is article

Domain: SEO & Structured Data Category: Structural Enforcement: Server Criticality: Low — incorrect OpenGraph type affects social sharing previews but does not break functionality.

Rule Statement: All item detail pages (/[page]) must set the OpenGraph type metadata to "article".

Conditions: None — this rule applies unconditionally to all item detail pages.

Validation Details:

Source Evidence: Item Detail (/[page]) — §8

Business Rationale: Treats all Shopify Pages (policy pages, about pages, FAQs) as article-type content for social sharing purposes, enabling richer link previews on platforms that support the article OpenGraph type.

Related Rules: BR-015, BR-016

Testing Implications:


BR-015: Item Detail OpenGraph Timestamps Sourced from Shopify

Domain: SEO & Structured Data Category: Structural Enforcement: Server Criticality: Low — incorrect timestamps affect social sharing metadata accuracy but do not break functionality.

Rule Statement: The OpenGraph publishedTime metadata for an item detail page must be sourced from page.createdAt, and modifiedTime must be sourced from page.updatedAt.

Conditions: None — this rule applies unconditionally.

Validation Details:

Source Evidence: Item Detail (/[page]) — §8

Business Rationale: Provides social platforms and search engines with accurate content freshness signals for Shopify page content.

Related Rules: BR-014, BR-028

Testing Implications:


BR-016: Home Page OpenGraph Type Is website

Domain: SEO & Structured Data Category: Structural Enforcement: Server Criticality: Low — incorrect OpenGraph type affects social sharing previews but does not break functionality.

Rule Statement: The home page (/) must set the OpenGraph type metadata to "website".

Conditions: None — this rule applies unconditionally to the home page.

Validation Details:

Source Evidence: Home (/) — §8

Business Rationale: Identifies the home page as a generic website page (as opposed to a product or article), which is the correct OpenGraph type for a storefront homepage.

Related Rules: BR-014

Testing Implications:


3.3: Product Display & Merchandising

Description: Rules governing how products are displayed, how many are shown, how empty states are handled, and how merchandising decisions are encoded in the UI.

Screens involved: Product Detail (/product/[handle]), Home (/), Search Detail (/search/[collection]), Search (/search)


Domain: Product Display & Merchandising Category: Structural Enforcement: Server Criticality: Medium — exceeding this limit is not possible given the implementation, but the rule defines the maximum gallery size regardless of how many images Shopify returns.

Rule Statement: The product gallery must display at most 5 images, regardless of the number of images returned by the Shopify API for a given product.

Conditions: None — this cap applies unconditionally to all products.

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §4, §8

Business Rationale: Not stated in documentation — verify with business stakeholders.

[Not documented — WHO: Product owner or UX designer; WHAT: What is the business rationale for capping the gallery at 5 images? Is this a UX decision, a performance decision, or a design constraint?; WHERE: Insert in the Business Rationale field of BR-017 above]

Related Rules: None

Testing Implications:


Domain: Product Display & Merchandising Category: Operative Enforcement: Server Criticality: Low — this is a UI cleanliness rule; no data or workflow is affected.

Rule Statement: The Related Products section must not render if getProductRecommendations returns an empty array.

Conditions:

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §5, §8, §10

Business Rationale: Prevents a visually empty "Related Products" section from appearing on product pages that have no algorithmic recommendations, keeping the page clean.

Related Rules: None

Testing Implications:


BR-019: Homepage Features Exactly Three Products in Hero Grid

Domain: Product Display & Merchandising Category: Structural Enforcement: Server Criticality: Medium — if fewer than three products are available, the ThreeItemGrid component's behavior is undefined at the page level; the grid may render incorrectly.

Rule Statement: The homepage hero grid must feature exactly three products, as determined by the ThreeItemGrid component's data source.

Conditions: None — the three-product constraint is structural to the ThreeItemGrid component.

Validation Details:

Source Evidence: Home (/) — §3, §8

Business Rationale: The three-product hero grid is a merchandising decision encoding a specific visual layout for the homepage above-the-fold section, giving maximum visual prominence to three curated products.

Related Rules: None

Testing Implications:

⚠️ This workflow continues beyond the documented screens. The internal data fetching and rendering logic of ThreeItemGrid (components/grid/three-items) is not covered in this document. Verify the complete behavior with the development team before treating this as a comprehensive description.


BR-020: Empty Collection Renders "No Products" Message

Domain: Product Display & Merchandising Category: Operative Enforcement: Server Criticality: Medium — without this rule, an empty collection would render a blank page with no feedback to the user.

Rule Statement: If a collection exists but contains zero products, the page must render the message "No products found in this collection" instead of a product grid.

Conditions:

Validation Details:

Source Evidence: Search Detail (/search/[collection]) — §3, §8, §10

Business Rationale: Provides clear user feedback when a collection has no products, rather than displaying a confusing blank page.

Related Rules: BR-021

Testing Implications:


BR-021: Empty Search Results Render "No Match" Message

Domain: Product Display & Merchandising Category: Operative Enforcement: Server Criticality: Medium — without this rule, a search with no results would render a blank page with no feedback.

Rule Statement: If a search query returns zero products, the page must render the message "There are no products that match " followed by the bolded search term, and must not render the product grid.

Conditions:

Validation Details:

Source Evidence: Search (/search) — §3, §8, §10

Business Rationale: Provides clear user feedback when a search query returns no matching products.

Related Rules: BR-020, BR-023, BR-024

Testing Implications:


BR-022: Search Result Count Uses Singular/Plural Grammar

Domain: Product Display & Merchandising Category: Operative Enforcement: Server Criticality: Low — this is a display quality rule; incorrect grammar does not affect functionality.

Rule Statement: The search results summary must display "result" (singular) when exactly one product is returned, and "results" (plural) when more than one product is returned.

Conditions:

Validation Details:

Source Evidence: Search (/search) — §4, §8

Business Rationale: Ensures grammatically correct display of result counts (e.g., "Showing 1 result for" vs. "Showing 3 results for").

Related Rules: BR-023

Testing Implications:


BR-023: Result Summary Suppressed When No Search Query

Domain: Product Display & Merchandising Category: Operative Enforcement: Server Criticality: Low — this is a UI cleanliness rule; no data or workflow is affected.

Rule Statement: The search results summary paragraph must not render when no search query (searchValue) is present.

Conditions:

Validation Details:

Source Evidence: Search (/search) — §3, §8

Business Rationale: When a user browses all products without a search query, displaying "Showing N results for [empty]" would be confusing and unhelpful.

Related Rules: BR-021, BR-022

Testing Implications:


BR-024: Product Grid Suppressed When No Results

Domain: Product Display & Merchandising Category: Operative Enforcement: Server Criticality: Low — this is a UI cleanliness rule preventing an empty grid container from rendering.

Rule Statement: The product grid (Grid and ProductGridItems components) must not render when products.length === 0.

Conditions:

Validation Details:

Source Evidence: Search (/search) — §3, §8

Business Rationale: Prevents an empty grid container from appearing in the DOM, which could cause layout issues or confuse users.

Related Rules: BR-021

Testing Implications:


3.4: Search & Sort

Description: Rules governing how sort parameters are resolved, validated, and applied to product listings.

Screens involved: Search Detail (/search/[collection]), Search (/search)


BR-025: Sort Parameter Resolves to Valid Sort Configuration

Domain: Search & Sort Category: Decision Enforcement: Server Criticality: High — if an invalid sort key were passed to the Shopify API, the API call could fail or return unexpected results, breaking the product listing.

Rule Statement: The sort URL query parameter must be resolved to a valid sortKey and reverse pair by matching against the sorting constants array; if no match is found, the defaultSort configuration must be used.

Conditions:

Validation Details:

Source Evidence: Search Detail (/search/[collection]) — §4, §7, §8; Search (/search) — §4, §7, §8

Business Rationale: Ensures that only valid Shopify ProductSortKeys enum values are ever passed to the Shopify Storefront API, preventing API errors from invalid sort parameters.

Related Rules: BR-026

Testing Implications:


BR-026: Unrecognized Sort Parameter Falls Back to Default

Domain: Search & Sort Category: Operative Enforcement: Server Criticality: Medium — without this rule, an unrecognized sort parameter could cause an API error or expose an invalid sort key to Shopify.

Rule Statement: When the sort URL parameter is absent or does not match any recognized sort slug, the application must silently apply the defaultSort configuration without displaying an error message to the user.

Conditions:

Validation Details:

Source Evidence: Search Detail (/search/[collection]) — §7, §8; Search (/search) — §7, §8

Business Rationale: Provides a graceful degradation path for invalid or missing sort parameters, ensuring the page always renders with a valid sort configuration rather than showing an error.

Related Rules: BR-025

Testing Implications:


3.5: Navigation & Routing

Description: Rules governing how navigation links are constructed and how prefetching is applied.

Screens involved: Product Detail (/product/[handle])


Domain: Navigation & Routing Category: Operative Enforcement: Client Criticality: Low — prefetch is a performance optimization; its absence would not break navigation, only slow it.

Rule Statement: Each related product tile must render as a Next.js <Link> pointing to /product/[product.handle] with prefetch={true} enabled.

Conditions: None — this rule applies to all related product tiles unconditionally.

Validation Details:

Source Evidence: Product Detail (/product/[handle]) — §6, §9

Business Rationale: Enables near-instant navigation to related product pages by prefetching their data when the tiles become visible, improving perceived performance.

Related Rules: None

Testing Implications:


3.6: Content Rendering

Description: Rules governing how content is formatted and rendered on the Item Detail screen.

Screens involved: Item Detail (/[page])


BR-028: Item Detail Date Formatted Using Server Locale

Domain: Content Rendering Category: Operative Enforcement: Server Criticality: Low — locale-dependent date formatting is a display quality concern; it does not affect data integrity or functionality.

Rule Statement: The updatedAt timestamp on an item detail page must be formatted as a human-readable date string using Intl.DateTimeFormat with year: "numeric", month: "long", and day: "numeric" options, using the server's default locale.

Conditions: None — this rule applies unconditionally to all item detail pages.

Validation Details:

Source Evidence: Item Detail (/[page]) — §3, §8

Business Rationale: Provides users with a human-readable "last updated" date for content pages such as privacy policies and FAQs, helping users assess content freshness.

Related Rules: BR-015

Testing Implications:


BR-029: Item Detail Page Body Rendered as Raw HTML

Domain: Content Rendering Category: Structural Enforcement: Server Criticality: Medium — raw HTML rendering from an external source carries XSS risk if the content pipeline is compromised; however, Shopify sanitizes page body content server-side.

Rule Statement: The body content of an item detail page must be rendered as raw HTML via the Prose component's html prop, using the HTML string returned by the Shopify Storefront API in page.body.

Conditions: None — this rule applies unconditionally.

Validation Details:

Source Evidence: Item Detail (/[page]) — §3, §14

Business Rationale: Shopify Pages are authored in a rich text editor that produces HTML output. Rendering the HTML directly preserves the merchant's formatting (headings, lists, links, bold/italic text) without requiring a custom content renderer.

Related Rules: None

Testing Implications:


4. Decision Tables

DT-01: Product SEO Indexability

Related Rules: BR-001, BR-009 Hit Policy: Unique

Condition: product.tags includes HIDDEN_PRODUCT_TAG indexable robots.index robots.follow → Page Accessible via URL
true false false false Yes
false true true (default) true (default) Yes

DT-02: Search Results Display State

Related Rules: BR-021, BR-023, BR-024 Hit Policy: Unique

Condition: searchValue present Condition: products.length > 0 → Summary Paragraph → Product Grid
false false Not rendered Not rendered
false true Not rendered Rendered
true false "No products that match [term]" Not rendered
true true "Showing N result(s) for [term]" Rendered

DT-03: SEO Metadata Title Resolution — Collection

Related Rules: BR-010 Hit Policy: First

Condition: collection.seo?.title truthy Condition: collection.title truthy → Page <title> Value
true collection.seo.title
false true collection.title
false false Empty / undefined

DT-04: SEO Metadata Description Resolution — Collection

Related Rules: BR-011 Hit Policy: First

Condition: collection.seo?.description truthy Condition: collection.description truthy Condition: collection.title truthy → Meta Description Value
true collection.seo.description
false true collection.description
false false true "[collection.title] products"
false false false " products" (empty prefix)

DT-05: SEO Metadata Resolution — Item Detail Page

Related Rules: BR-012, BR-013 Hit Policy: First

Field Condition: Primary Source Truthy Condition: Fallback Truthy → Value Used
<title> page.seo?.title is truthy page.seo.title
<title> page.seo?.title is falsy page.title is truthy page.title
<title> page.seo?.title is falsy page.title is falsy Empty
<meta description> page.seo?.description is truthy page.seo.description
<meta description> page.seo?.description is falsy page.bodySummary is truthy page.bodySummary
<meta description> page.seo?.description is falsy page.bodySummary is falsy Empty

DT-06: JSON-LD Availability Mapping

Related Rules: BR-006 Hit Policy: Unique

Condition: product.availableForSale → JSON-LD availability Value
true "https://schema.org/InStock"
false "https://schema.org/OutOfStock"
undefined "https://schema.org/OutOfStock" (inferred from JavaScript falsy evaluation — verify against getProduct return type definition in lib/shopify)

5. Validation Rules Summary

Rule ID Field / Input Validation Type Constraint Error Message / Behavior
BR-002 params.handle (URL segment) Custom getProduct(handle) must return a truthy value notFound() called → Next.js 404 page rendered; no user-facing error message
BR-003 params.collection (URL segment) Custom getCollection(collection) must return a truthy value notFound() called → Next.js 404 page rendered; no user-facing error message
BR-004 params.page (URL segment) Custom getPage(page) must return a truthy value notFound() called → Next.js 404 page rendered; no user-facing error message
BR-025 searchParams.sort (query param) Custom Value must match a slug in the sorting constants array Silent fallback to defaultSort; no error message shown to user
BR-001 product.tags array Custom If HIDDEN_PRODUCT_TAG present, indexable = false robots.index = false, robots.follow = false injected into <head>; page still renders

6. Calculation Rules Summary

Rule ID Output Formula / Logic Inputs Precision / Rounding
BR-006 JSON-LD availability URI availableForSale ? "https://schema.org/InStock" : "https://schema.org/OutOfStock" product.availableForSale (boolean) Not applicable
BR-001 indexable boolean !product.tags.includes(HIDDEN_PRODUCT_TAG) product.tags (string array), HIDDEN_PRODUCT_TAG (string constant) Not applicable
BR-022 resultsText string products.length > 1 ? "results" : "result" products.length (integer) Not applicable
BR-028 Formatted date string new Intl.DateTimeFormat(undefined, { year: "numeric", month: "long", day: "numeric" }).format(new Date(page.updatedAt)) page.updatedAt (ISO 8601 string) Not applicable — locale-dependent string output
BR-017 Gallery images array product.images.slice(0, 5).map(img => ({ src: img.url, altText: img.altText })) product.images array Not applicable — array truncation

7. Rule Dependency Map

Rule Depends On Type Description
BR-009 BR-001 Refinement BR-009 is the SEO metadata expression of the same HIDDEN_PRODUCT_TAG check defined in BR-001; both derive from the same indexable boolean
BR-007 BR-008 Complement BR-007 and BR-008 together define the complete AggregateOffer JSON-LD block; both must be satisfied for valid structured data
BR-006 BR-007 Complement BR-006 (availability) and BR-007 (price range) together populate the AggregateOffer object in the JSON-LD block
BR-026 BR-025 Refinement BR-026 is the fallback branch of the sort resolution logic defined in BR-025; BR-025 must be evaluated before BR-026 applies
BR-021 BR-024 Complement BR-021 (no-results message) and BR-024 (grid suppression) together define the complete empty-state rendering behavior for the Search screen
BR-023 BR-022 Prerequisite BR-023 must be satisfied (searchValue is truthy) before BR-022's resultsText calculation is rendered; if BR-023 suppresses the paragraph, BR-022's output is never displayed
BR-003 BR-020 Prerequisite BR-003 must pass (collection exists) before BR-020 can be evaluated; an invalid collection handle triggers a 404 before the empty-state check is reached
BR-004 BR-028 Prerequisite BR-004 must pass (page exists) before BR-028 can be evaluated; a missing page handle triggers a 404 before date formatting occurs
BR-004 BR-029 Prerequisite BR-004 must pass before BR-029 can be evaluated; a missing page handle triggers a 404 before body rendering occurs
BR-012 BR-013 Complement BR-012 and BR-013 together define the complete SEO metadata for item detail pages; both must be evaluated to produce a full <head> metadata set
BR-010 BR-011 Complement BR-010 and BR-011 together define the complete SEO metadata for collection pages
BR-015 BR-014 Complement BR-014 (type) and BR-015 (timestamps) together define the complete OpenGraph metadata for item detail pages

8. Gap Analysis

GAP-01: No Server-Side Enforcement of Image Cap

Type: Implicit Rule Affected Domain: Product Display & Merchandising Risk Level: Low Description: The 5-image cap (BR-017) is enforced by product.images.slice(0, 5) in the page component before passing the array to Gallery. However, the Shopify API query for product images may request more than 5 images (the documentation notes "images (up to 5+)"), meaning the API call fetches data that is then discarded. There is no documented enforcement of this cap at the API query level (e.g., requesting only 5 images from Shopify). This is an efficiency concern rather than a correctness concern. Recommendation: Verify whether the Shopify GraphQL query for product.images limits the result to 5 nodes at the query level (e.g., images(first: 5)). If not, consider adding this limit to the query to avoid fetching and discarding unnecessary data. Related Rules: BR-017


GAP-02: JSON-LD Script Injection Without HTML-Safe Serialization

Type: Implicit Rule Affected Domain: SEO & Structured Data Risk Level: Medium Description: The product JSON-LD block is injected via dangerouslySetInnerHTML={{ __html: JSON.stringify(productJsonLd) }}. The documentation explicitly notes that JSON.stringify does not escape HTML special characters (<, >, &), which could allow a product title or description containing </script> sequences to break out of the <script> tag. The data source is the Shopify API (not user input), which reduces the risk, but it is not zero — a compromised or misconfigured Shopify store could return malicious content. Recommendation: Add a rule requiring that the JSON-LD serialization use an HTML-safe JSON serializer that escapes <, >, and & characters (e.g., replacing < with \u003c). This should be enforced as a code-level standard for all dangerouslySetInnerHTML JSON-LD injections. Related Rules: BR-006, BR-007, BR-008


GAP-03: Missing Null Guard on featuredImage in JSON-LD Body

Type: Edge Case Affected Domain: SEO & Structured Data Risk Level: High Description: The documentation explicitly identifies that productJsonLd accesses product.featuredImage.url without a null guard in the page body. If featuredImage is null or undefined, this will throw a runtime error, causing the page to fail to render. The generateMetadata function does guard this with product.featuredImage || {}, but the page body does not apply the same guard. This is an inconsistency between the metadata and page rendering paths. Recommendation: Add a null guard for product.featuredImage in the productJsonLd construction: product.featuredImage?.url. Define a business rule that the JSON-LD image field must be omitted (not set to undefined) when featuredImage is absent. Related Rules: BR-006, BR-007


GAP-04: searchParams Type Cast Bypasses Array-Value Safety

Type: Edge Case Affected Domain: Search & Sort Risk Level: Low Description: On both the Search (/search) and Search Detail (/search/[collection]) screens, searchParams is cast with as { [key: string]: string }. In Next.js App Router, searchParams values can be string | string[] when the same query parameter appears multiple times in the URL (e.g., ?sort=price-asc&sort=trending). The type cast bypasses TypeScript's type safety for this case. If sort or q were passed as arrays, the behavior would be undefined — the array object would be passed to getProducts or the sort lookup, producing unexpected results. The sort fallback (BR-026) would handle the sort case gracefully, but the q parameter passed as an array to getProducts could produce unexpected API behavior. Recommendation: Add runtime validation of searchParams values to ensure they are strings before use, rather than relying on a type cast. Define a rule that multi-value query parameters for q and sort must be handled by taking the first value or rejecting the request. Related Rules: BR-025, BR-026


GAP-05: getPage Called Twice Per Request Without Visible Deduplication

Type: Implicit Rule Affected Domain: Content Rendering Risk Level: Low Description: On the Item Detail screen, getPage is called independently in both generateMetadata and the Page component, resulting in two separate Shopify API calls per page request. The documentation notes that Next.js's fetch deduplication may apply if getPage uses the native fetch API internally, but this is not confirmed. If deduplication is not active, this doubles the API call volume for every item detail page render, which could contribute to Shopify API rate limit consumption. Recommendation: Verify whether getPage in lib/shopify uses the native fetch API with Next.js's automatic request deduplication. If not, consider refactoring to use React's cache() function to deduplicate the call within a single render pass. Define a rule that each Shopify API function must be called at most once per page render. Related Rules: BR-004, BR-012

[Not documented — WHO: Development team; WHAT: Does getPage in lib/shopify use the native fetch API, and is Next.js request deduplication active for this call?; WHERE: Insert in the Description field of GAP-05 above]


GAP-06: No Empty-State UI for Home Page Components

Type: Missing Rule Affected Domain: Product Display & Merchandising Risk Level: Medium Description: The Home screen documentation states that if ThreeItemGrid fetches zero products (e.g., an empty Shopify collection), the grid component is responsible for rendering an appropriate empty state — but no empty-state behavior is documented for ThreeItemGrid or Carousel at the page level. There is no documented rule for what the homepage should display if the featured collection is empty or if the Shopify API returns no products. This is a gap compared to the Search and Search Detail screens, which have explicit empty-state rules (BR-020, BR-021). Recommendation: Document the expected empty-state behavior for ThreeItemGrid and Carousel. Define a rule specifying what the homepage renders when the featured collection contains fewer than three products. Related Rules: BR-019


GAP-07: Malformed updatedAt Produces Visible "Invalid Date" String

Type: Edge Case Affected Domain: Content Rendering Risk Level: Medium Description: The Item Detail screen formats page.updatedAt using new Intl.DateTimeFormat(...).format(new Date(page.updatedAt)). The documentation explicitly identifies that if page.updatedAt is malformed, new Date(malformed) produces an Invalid Date object, and Intl.DateTimeFormat.format() outputs the string "Invalid Date" visibly on the page. There is no guard or fallback for this case. Recommendation: Add a rule requiring that the updatedAt date be validated before formatting. If the date is invalid, the "last updated" paragraph should either be omitted or display a fallback string. Implement a guard such as const date = new Date(page.updatedAt); if (!isNaN(date.getTime())) { /* render date */ }. Related Rules: BR-028


GAP-08: Missing page.title Has No Fallback

Type: Edge Case Affected Domain: Content Rendering Risk Level: Low Description: The Item Detail screen renders page.title directly in an <h1> element with no fallback. The documentation explicitly notes: "If page.title is missing, <h1> renders empty; no fallback is implemented." An empty <h1> is both a UX problem (blank heading) and an accessibility concern (screen readers announce an empty heading). This contrasts with the SEO metadata, which has a two-tier fallback (BR-012). Recommendation: Define a rule that the <h1> heading must not render empty. Add a fallback (e.g., a placeholder string, or suppress the <h1> if page.title is falsy). Related Rules: BR-012, BR-004


GAP-09: HIDDEN_PRODUCT_TAG Constant Value Not Documented

Type: Missing Rule Affected Domain: Content Visibility & Access Control Risk Level: Medium Description: The HIDDEN_PRODUCT_TAG constant is referenced in BR-001 and BR-009 as the mechanism for suppressing SEO indexing of draft or internal products. However, the actual string value of this constant is defined in lib/constants and is not documented in the screen documentation. Without knowing the exact tag value, merchants cannot correctly apply the tag in Shopify's admin, and QA engineers cannot test the behavior. Recommendation: Document the exact string value of HIDDEN_PRODUCT_TAG from lib/constants. Add this value to the Glossary and to the rule entries for BR-001 and BR-009.

[Not documented — WHO: Development team; WHAT: What is the exact string value of the HIDDEN_PRODUCT_TAG constant in lib/constants?; WHERE: Insert in the Glossary entry for HIDDEN_PRODUCT_TAG and in the Validation Details of BR-001 and BR-009]

Related Rules: BR-001, BR-009


GAP-10: No Rate Limiting Documented for Any Public API Call

Type: Missing Rule Affected Domain: Content Visibility & Access Control Risk Level: Medium Description: None of the five assessed screens document any rate limiting, request throttling, or abuse prevention for the Shopify Storefront API calls they make. All five screens are publicly accessible without authentication, meaning any visitor can trigger server-side Shopify API calls by requesting these pages. High-volume requests (e.g., from scrapers or denial-of-service attempts) could exhaust Shopify API rate limits or increase infrastructure costs. The documentation notes that rate limiting, if any, would be enforced by the Shopify Storefront API itself or at the Vercel platform level — but no such configuration is documented. Recommendation: Document whether rate limiting is configured at the Vercel edge, Next.js middleware, or Shopify API level. If no rate limiting is in place, define a rule specifying acceptable request rates and implement appropriate controls.

[Not documented — WHO: Infrastructure/DevOps team; WHAT: Is rate limiting configured at the Vercel edge, Next.js middleware, or any other layer for requests to the assessed screens?; WHERE: Insert in the Description field of GAP-10 above and create a new rule entry if rate limiting is confirmed]

Related Rules: BR-005


9. Glossary

9.1: Business Concepts

Term Definition in this context
Collection A Shopify grouping of products, identified by a URL-safe handle string (e.g., "t-shirts", "new-arrivals"). Equivalent to a product category. Used as the [collection] route segment on the Search Detail screen.
Shopify Page A content entity in Shopify's admin (distinct from product or collection pages) used for static informational content such as privacy policies, FAQs, and about pages. Rendered by the Item Detail screen.
availableForSale A Shopify boolean field on a product indicating whether any variant of the product is currently purchasable. Used to determine the JSON-LD availability value.
featuredImage The primary/hero image of a Shopify product, used for Open Graph tags, JSON-LD structured data, and as the default display image.
priceRange A Shopify object containing minVariantPrice and maxVariantPrice, each with amount (string) and currencyCode (e.g., "USD"). Represents the price spread across all product variants.
HIDDEN_PRODUCT_TAG A Shopify product tag (string constant from lib/constants) that marks a product as non-indexable by search engines. Products with this tag are accessible by URL but excluded from SEO crawling. The exact string value is defined in lib/constants.
defaultSort The fallback sort configuration from lib/constants, used when the sort URL parameter is absent or unrecognized. Defines a sortKey and reverse value.
sorting An array of sort option objects defined in lib/constants, each containing a URL-safe slug, a Shopify sortKey enum value, and a reverse boolean. Maps user-facing sort options to Shopify API parameters.
handle A URL-safe, human-readable string identifier for a Shopify resource (product, collection, or page). Unique per resource type. Used as the dynamic route segment on Product Detail, Search Detail, and Item Detail screens.
page.body The full HTML content of a Shopify Page, authored in Shopify's rich text editor and returned as an HTML string by the Storefront API.
page.bodySummary A plain-text excerpt or summary of a Shopify Page body, provided by Shopify and used as a fallback for the SEO meta description on Item Detail pages.
page.seo An optional object on a Shopify Page containing title and description fields that override the default title and body summary for search engine metadata.
indexable A derived boolean computed at render time on the Product Detail screen. true if the product does not carry HIDDEN_PRODUCT_TAG; false if it does. Controls the robots metadata directives.

9.2: Rule Terminology

Term Definition in this context
Structural rule A BABOK v3.0 rule category. Defines constraints on data or entities — what must be true about the structure of data. Example: "A product gallery must contain at most 5 images."
Operative rule A BABOK v3.0 rule category. Governs processes or actions — what must happen under certain conditions. Example: "If a product handle is not found, the application must render a 404 page."
Decision rule A BABOK v3.0 rule category. Defines condition-based outcomes — if [condition], then [result]. Example: "If availableForSale is true, then JSON-LD availability is InStock."
Hit Policy A DMN v1.5 attribute on a decision table specifying how overlapping rows are resolved. Unique: only one row can match. First: first matching row wins. Any: multiple rows may match but produce the same result. Collect: all matching rows are applied.
Enforcement point Where a rule is evaluated and enforced. Client: in the browser (UI/form). Server: on the server (API/backend). Both: enforced at both layers.
Criticality The severity of impact if a rule is violated. Critical: data corruption or security breach. High: broken workflows or incorrect business behavior. Medium: degraded user experience. Low: cosmetic or minor quality issues.

9.3: Domain Terms

Term Definition in this context
AggregateOffer A schema.org type used in JSON-LD to represent a product available at a range of prices (across variants), as opposed to a single fixed price (Offer). Used in the product JSON-LD block to represent multi-variant pricing.
JSON-LD JavaScript Object Notation for Linked Data — a structured data format embedded in a <script type="application/ld+json"> tag that search engines (especially Google) use to understand page content and generate rich search results.
sortKey A Shopify Storefront API enum value (e.g., PRICE, BEST_SELLING, CREATED_AT, TITLE) that determines the field by which products are sorted in collection and search queries.
reverse A boolean passed to the Shopify Storefront API that inverts the sort direction when true (e.g., highest price first instead of lowest).
slug A URL-safe string identifier for a sort option (e.g., "price-asc", "trending"), used to match the ?sort= query parameter to a sort configuration object in the sorting constants array.
Prose An internal component (components/prose) that renders a raw HTML string with typographic styling applied, similar to Tailwind CSS's prose utility class pattern. Used on the Item Detail screen to render page.body.
notFound() A Next.js App Router function from next/navigation that, when called, interrupts rendering and triggers the nearest not-found.tsx boundary, resulting in a 404 HTTP response.
generateMetadata A Next.js App Router convention — an exported async function from a page file that returns a Metadata object used to populate <head> tags (title, description, Open Graph, robots directives, etc.).
ThreeItemGrid A custom component (components/grid/three-items) that renders a curated grid of exactly three featured products, used as the hero/above-the-fold section of the storefront homepage.
Carousel A custom component (components/carousel) that renders a horizontally scrollable list of products, used on the homepage to expose a broader product catalog.
ProductGridItems A component (components/layout/product-grid-items) that maps over a products array and renders individual product cards in a grid layout, including image, title, and price.
GridTileImage A reusable image tile component (components/grid/tile) used in grid and list layouts, supporting a label overlay with title and price. Used for related product tiles on the Product Detail screen.
RelatedProducts An async React Server Component on the Product Detail screen that fetches and renders Shopify's algorithmic product recommendations for the current product. Returns null if no recommendations exist.
Open Graph A metadata protocol (originally by Facebook) that controls how a URL is represented when shared on social media platforms. This application sets og:type, og:published_time, and og:modified_time on relevant screens.
Intl.DateTimeFormat A built-in JavaScript API for locale-aware date and time formatting. Used on the Item Detail screen to render page.updatedAt as a human-readable string using the server's default locale.

End of Business Rules Assessment — Commerce Application — May 2026