@wix/web5-core 1.63.14 → 1.63.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cjs/component/componentDefinitions/TextBlockSectionDefinition.js +36 -32
  2. package/dist/cjs/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
  3. package/dist/cjs/component/componentDefinitions/index.js +5 -4
  4. package/dist/cjs/component/componentDefinitions/index.js.map +1 -1
  5. package/dist/cjs/component/componentDefinitions/parse-utils.js +43 -6
  6. package/dist/cjs/component/componentDefinitions/parse-utils.js.map +1 -1
  7. package/dist/cjs/entity/matchedVariant.js +31 -9
  8. package/dist/cjs/entity/matchedVariant.js.map +1 -1
  9. package/dist/cjs/hooks/useResolveShopifyEntityData.js +6 -0
  10. package/dist/cjs/hooks/useResolveShopifyEntityData.js.map +1 -1
  11. package/dist/cjs/match/astToParts.js +14 -0
  12. package/dist/cjs/match/astToParts.js.map +1 -1
  13. package/dist/cjs/services/shopify/transformShopifyEntity.js +17 -5
  14. package/dist/cjs/services/shopify/transformShopifyEntity.js.map +1 -1
  15. package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js +37 -33
  16. package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
  17. package/dist/esm/component/componentDefinitions/index.js +5 -4
  18. package/dist/esm/component/componentDefinitions/index.js.map +1 -1
  19. package/dist/esm/component/componentDefinitions/parse-utils.js +42 -6
  20. package/dist/esm/component/componentDefinitions/parse-utils.js.map +1 -1
  21. package/dist/esm/entity/matchedVariant.js +31 -9
  22. package/dist/esm/entity/matchedVariant.js.map +1 -1
  23. package/dist/esm/hooks/useResolveShopifyEntityData.js +6 -0
  24. package/dist/esm/hooks/useResolveShopifyEntityData.js.map +1 -1
  25. package/dist/esm/match/astToParts.js +14 -0
  26. package/dist/esm/match/astToParts.js.map +1 -1
  27. package/dist/esm/services/shopify/transformShopifyEntity.js +17 -5
  28. package/dist/esm/services/shopify/transformShopifyEntity.js.map +1 -1
  29. package/dist/types/component/componentDefinitions/TextBlockSectionDefinition.d.ts.map +1 -1
  30. package/dist/types/component/componentDefinitions/index.d.ts.map +1 -1
  31. package/dist/types/component/componentDefinitions/parse-utils.d.ts +21 -0
  32. package/dist/types/component/componentDefinitions/parse-utils.d.ts.map +1 -1
  33. package/dist/types/entity/matchedVariant.d.ts +18 -9
  34. package/dist/types/entity/matchedVariant.d.ts.map +1 -1
  35. package/dist/types/hooks/useResolveShopifyEntityData.d.ts.map +1 -1
  36. package/dist/types/match/astToParts.d.ts.map +1 -1
  37. package/dist/types/services/shopify/transformShopifyEntity.d.ts.map +1 -1
  38. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"names":["readText","value","trim","Number","isFinite","String","toMatchedOptions","raw","Array","isArray","undefined","pairs","rawName","rawValue","Object","entries","name","push","length","formatMoney","amount","currency","Intl","NumberFormat","style","minimumFractionDigits","isInteger","format","readCurrencyCode","candidates","candidate","code","toUpperCase","test","formatPriceField","formatProductPriceLabel","fields","price","priceMin","priceMax"],"sources":["../../../src/entity/matchedVariant.ts"],"sourcesContent":["/**\n * Presentation helpers for a document that retrieval resolved down to one\n * child.\n *\n * When a query filters on an attribute that lives on the child rather than on\n * the parent, retrieval overlays the matching child's presentation fields onto\n * the parent document before it reaches us: `url`, `img` and `price` already\n * describe the child, while `title`, `description` and `doc_id` stay the\n * parent's. Two fields need shaping before a card can print them, and both\n * live here:\n *\n * - `options` — the child's own `{ name: value }` pairs.\n * - `priceMin` / `priceMax` — the parent's span, against which the child's\n * single `price` has to be read.\n *\n * The overlay itself is done server-side and is deliberately NOT repeated\n * here.\n */\n\n/** One `{ name: value }` pair, exactly as the store named it. */\nexport interface MatchedOption {\n name: string;\n value: string;\n}\n\nfunction readText(value: unknown): string {\n if (typeof value === 'string') {\n return value.trim();\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return String(value);\n }\n return '';\n}\n\n/**\n * Turn a document's raw `options` map into an ordered list of pairs.\n *\n * Every entry the store put in the map is kept, under the store's own name and\n * in the store's own order. Nothing here knows — or may learn — what any\n * particular name means: one catalogue's pairs describe a garment, the next\n * one's a power tool, and both have to come out identical. Entries with an\n * empty name or an empty/non-printable value are dropped, since a card can do\n * nothing with them.\n *\n * Returns `undefined` (not `[]`) when there is nothing to show, so callers can\n * treat it like every other optional field on the entity.\n */\nexport function toMatchedOptions(raw: unknown): MatchedOption[] | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return undefined;\n }\n\n const pairs: MatchedOption[] = [];\n for (const [rawName, rawValue] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n const name = rawName.trim();\n const value = readText(rawValue);\n if (name && value) {\n pairs.push({ name, value });\n }\n }\n\n return pairs.length ? pairs : undefined;\n}\n\n/**\n * Render one amount in the document's currency.\n *\n * With no currency code on the document the amount is printed bare: assuming\n * \"$\" prints a false fact on every store that doesn't sell in dollars. Whole\n * amounts stay whole (\"107\" not \"107.00\"); a real fractional amount keeps its\n * minor units.\n */\nexport function formatMoney(amount: number, currency?: string): string {\n if (!currency) {\n return String(amount);\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,\n }).format(amount);\n } catch {\n // A well-formed but unknown code: show it rather than dropping it.\n return `${amount} ${currency}`;\n }\n}\n\n/** Normalize a document currency to an ISO-4217 code, or drop it. */\nexport function readCurrencyCode(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n if (typeof candidate !== 'string') {\n continue;\n }\n const code = candidate.trim().toUpperCase();\n if (/^[A-Z]{3}$/.test(code)) {\n return code;\n }\n }\n return undefined;\n}\n\n/**\n * Format one of a document's price fields for display. Numbers are rendered in\n * the document's currency; an already-formatted string is passed through\n * untouched; anything else yields `undefined`.\n */\nexport function formatPriceField(\n value: unknown,\n currency?: string,\n): string | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return formatMoney(value, currency);\n }\n if (typeof value === 'string' && value.trim()) {\n return value.trim();\n }\n return undefined;\n}\n\n/** The already-formatted price fields a card reads. */\nexport interface ProductPriceFields {\n /** Price of the one variant this result matched. */\n price?: string;\n /** Lowest price across the whole product's variants. */\n priceMin?: string;\n /** Highest price across the whole product's variants. */\n priceMax?: string;\n}\n\n/**\n * What a product card should print as its price.\n *\n * `price` belongs to the single variant the search matched — the same variant\n * the card's image and link point at. `priceMin`/`priceMax` describe the whole\n * product. When those two differ the product has no one price, and printing\n * the matched variant's number on its own would pass it off as the product's;\n * printing it after \"from\" would be worse still, because \"from\" is a claim\n * about the floor of the span and the matched variant is not always the\n * cheapest one. So a span prints its own floor, prefixed.\n *\n * With no span — the two ends equal, or only one of them known — nothing\n * changes: the matched variant's price is printed as it always was.\n */\nexport function formatProductPriceLabel(\n fields: ProductPriceFields,\n): string | undefined {\n const { price, priceMin, priceMax } = fields;\n if (priceMin && priceMax && priceMin !== priceMax) {\n return `from ${priceMin}`;\n }\n return price;\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["readText","value","trim","Number","isFinite","String","toMatchedOptions","raw","Array","isArray","undefined","pairs","rawName","rawValue","Object","entries","name","push","length","formatMoney","amount","currency","Intl","NumberFormat","style","minimumFractionDigits","isInteger","format","readCurrencyCode","candidates","candidate","code","toUpperCase","test","formatPriceField","namesOneVariant","fields","_fields$matchedOption","Boolean","variantId","matchedOptions","formatProductPriceLabel","price","priceMin","priceMax"],"sources":["../../../src/entity/matchedVariant.ts"],"sourcesContent":["/**\n * Presentation helpers for a document that retrieval resolved down to one\n * child.\n *\n * When a query filters on an attribute that lives on the child rather than on\n * the parent, retrieval overlays the matching child's presentation fields onto\n * the parent document before it reaches us: `url`, `img` and `price` already\n * describe the child, while `title`, `description` and `doc_id` stay the\n * parent's. Two fields need shaping before a card can print them, and both\n * live here:\n *\n * - `options` — the child's own `{ name: value }` pairs.\n * - `priceMin` / `priceMax` — the parent's span, against which the child's\n * single `price` has to be read.\n *\n * The overlay itself is done server-side and is deliberately NOT repeated\n * here.\n */\n\n/** One `{ name: value }` pair, exactly as the store named it. */\nexport interface MatchedOption {\n name: string;\n value: string;\n}\n\nfunction readText(value: unknown): string {\n if (typeof value === 'string') {\n return value.trim();\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return String(value);\n }\n return '';\n}\n\n/**\n * Turn a document's raw `options` map into an ordered list of pairs.\n *\n * Every entry the store put in the map is kept, under the store's own name and\n * in the store's own order. Nothing here knows — or may learn — what any\n * particular name means: one catalogue's pairs describe a garment, the next\n * one's a power tool, and both have to come out identical. Entries with an\n * empty name or an empty/non-printable value are dropped, since a card can do\n * nothing with them.\n *\n * Returns `undefined` (not `[]`) when there is nothing to show, so callers can\n * treat it like every other optional field on the entity.\n */\nexport function toMatchedOptions(raw: unknown): MatchedOption[] | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return undefined;\n }\n\n const pairs: MatchedOption[] = [];\n for (const [rawName, rawValue] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n const name = rawName.trim();\n const value = readText(rawValue);\n if (name && value) {\n pairs.push({ name, value });\n }\n }\n\n return pairs.length ? pairs : undefined;\n}\n\n/**\n * Render one amount in the document's currency.\n *\n * With no currency code on the document the amount is printed bare: assuming\n * \"$\" prints a false fact on every store that doesn't sell in dollars. Whole\n * amounts stay whole (\"107\" not \"107.00\"); a real fractional amount keeps its\n * minor units.\n */\nexport function formatMoney(amount: number, currency?: string): string {\n if (!currency) {\n return String(amount);\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,\n }).format(amount);\n } catch {\n // A well-formed but unknown code: show it rather than dropping it.\n return `${amount} ${currency}`;\n }\n}\n\n/** Normalize a document currency to an ISO-4217 code, or drop it. */\nexport function readCurrencyCode(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n if (typeof candidate !== 'string') {\n continue;\n }\n const code = candidate.trim().toUpperCase();\n if (/^[A-Z]{3}$/.test(code)) {\n return code;\n }\n }\n return undefined;\n}\n\n/**\n * Format one of a document's price fields for display. Numbers are rendered in\n * the document's currency; an already-formatted string is passed through\n * untouched; anything else yields `undefined`.\n */\nexport function formatPriceField(\n value: unknown,\n currency?: string,\n): string | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return formatMoney(value, currency);\n }\n if (typeof value === 'string' && value.trim()) {\n return value.trim();\n }\n return undefined;\n}\n\n/** The already-formatted price fields a card reads. */\nexport interface ProductPriceFields {\n /** Price of the one variant this result matched. */\n price?: string;\n /** Lowest price across the whole product's variants. */\n priceMin?: string;\n /** Highest price across the whole product's variants. */\n priceMax?: string;\n /** Id of the variant this row was resolved down to, when there was one. */\n variantId?: string;\n /** The pairs naming that variant — what the card prints above the price. */\n matchedOptions?: MatchedOption[];\n}\n\n/**\n * Whether this row stands for one variant rather than for the whole product.\n *\n * Either mark is enough. `variantId` is the machine answer and `matchedOptions`\n * the human one, and a row can arrive with only one of them: retrieval that\n * resolved a document down to a child sends the pairs, while a row whose\n * identity came from the `?variant=` on its own URL has only the id. Both mean\n * the same thing — the card names a child, so the card must price that child.\n */\nfunction namesOneVariant(fields: ProductPriceFields): boolean {\n return Boolean(fields.variantId) || Boolean(fields.matchedOptions?.length);\n}\n\n/**\n * What a product card should print as its price.\n *\n * A card that names a variant prices THAT variant. `price` belongs to the\n * single child the search resolved to — the same child the card's image, its\n * `?variant=` link and the pairs printed above the price all describe — so it\n * is the only number that can follow them without contradicting them. \"from\"\n * is wrong there twice over: it claims a range where the card named one thing,\n * and the floor it points at is a different child's price wearing this one's\n * name. That is what shipped: five colourways of one shoe, each naming its own\n * colour and each printing the cheapest colour's price.\n *\n * `priceMin`/`priceMax` describe the whole product, and they are the answer\n * only when the row IS the whole product. Then the span's floor prints\n * prefixed, because no single number can stand for a product that has several.\n *\n * With no span — the ends equal, or only one of them known — the row's own\n * price prints as it always did.\n */\nexport function formatProductPriceLabel(\n fields: ProductPriceFields,\n): string | undefined {\n const { price, priceMin, priceMax } = fields;\n if (price && namesOneVariant(fields)) {\n return price;\n }\n if (priceMin && priceMax && priceMin !== priceMax) {\n return `from ${priceMin}`;\n }\n return price;\n}\n"],"mappings":"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","ignoreList":[]}
@@ -107,6 +107,12 @@ function mergeShopifyEntityData(existing, resolved) {
107
107
  url: existing.url || resolved.url,
108
108
  title: existing.title || resolved.title,
109
109
  imageUrl: existing.imageUrl || resolved.imageUrl,
110
+ // The live fetch withholds a price when it was asked about a variant it
111
+ // could not find, rather than answering with the product's floor. Its
112
+ // silence must not erase the turn's number, which was about the right
113
+ // child; a spread would overwrite with `undefined` and leave the card
114
+ // priceless.
115
+ price: resolved.price ?? existing.price,
110
116
  variantId: existing.variantId || resolved.variantId,
111
117
  matchedOptions: (_existing$matchedOpti = existing.matchedOptions) != null && _existing$matchedOpti.length ? existing.matchedOptions : resolved.matchedOptions
112
118
  };
@@ -1 +1 @@
1
- {"version":3,"names":["_react","require","_shopifyStorefrontClient","_shopifyEntityResolver","_transformShopifyEntity","shopifyEntityCache","Map","cacheKey","entityType","entityId","variantId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","variantIdFromUrl","_URLSearchParams$get","query","URLSearchParams","get","getShopifyLookup","entity","_entity$data","_entity$data2","_entity$data3","urlHandle","data","mergeShopifyEntityData","existing","resolved","_existing$matchedOpti","title","imageUrl","matchedOptions","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","useState","settled","setSettled","clientRef","useRef","configKeyRef","configKey","storeDomain","apiVersion","current","ShopifyStorefrontClient","entityIds","useMemo","map","e","lookup","join","useEffect","ignore","client","entityLookups","key","toFetch","filter","has","cached","forEach","item","set","fetchAll","Promise","allSettled","resolveShopifyEntity","itemData","transformShopifyEntityToItemData","finalMap","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId:variantId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(\n entityType: string,\n entityId: string,\n variantId?: string,\n): string {\n return `${entityType}:${entityId}:${variantId || ''}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n // A variant is not a Storefront resource of its own — there is no\n // `variantByHandle`. It is fetched as its parent product and then priced at\n // the variant the lookup carries alongside, which is why `variant` and\n // `product` normalize to the same query here rather than to two.\n if (lower.includes('product') || lower.includes('variant')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\n/**\n * The `?variant=` the row's own URL already carries.\n *\n * A row that names a variant — whether the turn overlaid one onto a product or\n * sent the variant as the entity itself — always links to it, because that is\n * the URL a shopper has to land on. So when the row omits `variantId` as a\n * field, the URL still says which child it means, and pricing the parent's\n * default instead would contradict the link on the same card.\n */\nfunction variantIdFromUrl(url: string | undefined): string | undefined {\n const query = url?.split('#')[0].split('?')[1];\n if (!query) {\n return undefined;\n }\n return new URLSearchParams(query).get('variant')?.trim() || undefined;\n}\n\nexport function getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return {\n entityType,\n entityId,\n variantId: entity.data?.variantId ?? variantIdFromUrl(entity.data?.url),\n };\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything the Storefront answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${cacheKey(\n lookup.entityType,\n lookup.entityId,\n lookup.variantId,\n )}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n // The variant is part of the key: the cached entry carries that\n // variant's live price, so two entities on the same product but\n // different variants must not share it.\n key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n lookup.variantId,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_react","require","_shopifyStorefrontClient","_shopifyEntityResolver","_transformShopifyEntity","shopifyEntityCache","Map","cacheKey","entityType","entityId","variantId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","variantIdFromUrl","_URLSearchParams$get","query","URLSearchParams","get","getShopifyLookup","entity","_entity$data","_entity$data2","_entity$data3","urlHandle","data","mergeShopifyEntityData","existing","resolved","_existing$matchedOpti","title","imageUrl","price","matchedOptions","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","useState","settled","setSettled","clientRef","useRef","configKeyRef","configKey","storeDomain","apiVersion","current","ShopifyStorefrontClient","entityIds","useMemo","map","e","lookup","join","useEffect","ignore","client","entityLookups","key","toFetch","filter","has","cached","forEach","item","set","fetchAll","Promise","allSettled","resolveShopifyEntity","itemData","transformShopifyEntityToItemData","finalMap","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId:variantId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(\n entityType: string,\n entityId: string,\n variantId?: string,\n): string {\n return `${entityType}:${entityId}:${variantId || ''}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n // A variant is not a Storefront resource of its own — there is no\n // `variantByHandle`. It is fetched as its parent product and then priced at\n // the variant the lookup carries alongside, which is why `variant` and\n // `product` normalize to the same query here rather than to two.\n if (lower.includes('product') || lower.includes('variant')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\n/**\n * The `?variant=` the row's own URL already carries.\n *\n * A row that names a variant — whether the turn overlaid one onto a product or\n * sent the variant as the entity itself — always links to it, because that is\n * the URL a shopper has to land on. So when the row omits `variantId` as a\n * field, the URL still says which child it means, and pricing the parent's\n * default instead would contradict the link on the same card.\n */\nfunction variantIdFromUrl(url: string | undefined): string | undefined {\n const query = url?.split('#')[0].split('?')[1];\n if (!query) {\n return undefined;\n }\n return new URLSearchParams(query).get('variant')?.trim() || undefined;\n}\n\nexport function getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return {\n entityType,\n entityId,\n variantId: entity.data?.variantId ?? variantIdFromUrl(entity.data?.url),\n };\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything the Storefront answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n // The live fetch withholds a price when it was asked about a variant it\n // could not find, rather than answering with the product's floor. Its\n // silence must not erase the turn's number, which was about the right\n // child; a spread would overwrite with `undefined` and leave the card\n // priceless.\n price: resolved.price ?? existing.price,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${cacheKey(\n lookup.entityType,\n lookup.entityId,\n lookup.variantId,\n )}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n // The variant is part of the key: the cached entry carries that\n // variant's live price, so two entities on the same product but\n // different variants must not share it.\n key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n lookup.variantId,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
@@ -333,6 +333,13 @@ function nodesToParts(nodes) {
333
333
  const parts = [];
334
334
  const typeCounts = {};
335
335
  const childCursors = new Map();
336
+ // A paragraph carrying an inline link is unwrapped into several block
337
+ // elements (`t`, `link`, `t`), so its parts are siblings with nothing to say
338
+ // they came from one paragraph. Mark every part after the first of a given
339
+ // AST node, so a consumer rebuilding prose can tell "same paragraph, keep
340
+ // going" from "next paragraph, break". Only split nodes carry the flag —
341
+ // plain paragraphs, headings and images stay meta-free.
342
+ const seenNodes = new Set();
336
343
  for (const block of enrichedBlocks) {
337
344
  const astNode = nodes[block.astNodeIndex];
338
345
  const type = blockElementToPartType(block.element);
@@ -356,6 +363,13 @@ function nodesToParts(nodes) {
356
363
  markdown: source.markdown.trim()
357
364
  };
358
365
  }
366
+ if (seenNodes.has(block.astNodeIndex)) {
367
+ part.meta = {
368
+ ...part.meta,
369
+ continuesNode: true
370
+ };
371
+ }
372
+ seenNodes.add(block.astNodeIndex);
359
373
 
360
374
  // For lists whose items are all complex, the list-level content is
361
375
  // meaningless (just a concatenation of child text). Clear it so that
@@ -1 +1 @@
1
- {"version":3,"names":["_markdownBlocks","require","_parts","_parseUtils","_entityLinkParser","_linkTypes","blockElementToPartType","element","startsWith","findNode","node","type","Array","isArray","children","child","found","extractTableCellLabel","parsed","url","parseWeb5Url","Web5UrlType","ICON","alt","value","map","join","tableCellMeta","cell","label","trim","link","href","undefined","image","imageUrl","iconParsed","ENTITY","entityType","entityId","iconHint","path","resolveSource","block","astNode","cursors","n","content","extractContent","md","extractContentMarkdown","markdown","cursor","get","astNodeIndex","target","i","length","set","text","buildListItems","listNode","item","itemParts","nodesToParts","isComplex","part","role","meta","parts","buildMeta","sourceNode","level","depth","lm","linkMetadata","title","isEntity","img","src","iconImg","iconUrl","web5Match","match","items","ordered","rows","headerRow","headerCells","columnLabels","featureColumnLabel","dataRows","slice","row","cells","firstCell","values","feature","columns","raw","kind","parseCalloutTag","htmlCommentMetadata","nodes","enrichedBlocks","convertToBlockElements","typeCounts","childCursors","Map","count","source","deriveRole","allComplex","every","it","push"],"sources":["../../../src/match/astToParts.ts"],"sourcesContent":["import type { RootContent } from 'mdast';\nimport { convertToBlockElements } from '../markdownBlocks';\nimport type {\n EnrichedBlockElement,\n BlockElement,\n} from '../patternValidator/types';\nimport {\n type Part,\n type PartType,\n extractContent,\n extractContentMarkdown,\n deriveRole,\n} from '../parts/parts';\nimport { parseCalloutTag } from '../component/componentDefinitions/parse-utils';\nimport { parseWeb5Url } from '../utils/entityLinkParser';\nimport { Web5UrlType } from '../types/link-types';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction blockElementToPartType(element: BlockElement | string): PartType {\n if (typeof element === 'string' && element.startsWith('list{')) {\n return 'list';\n }\n switch (element) {\n case 'h1':\n case 'h2':\n case 'h3':\n case 'h4':\n case 'h5':\n case 'h6':\n return 'heading';\n case 't':\n return 'text';\n case 'link':\n return 'link';\n case 'image':\n return 'image';\n case 'icon':\n return 'icon';\n case 'list':\n return 'list';\n case 'table':\n return 'table';\n case 'blockquote':\n return 'blockquote';\n case 'callout':\n return 'callout';\n case 'html_comment':\n return 'htmlComment';\n case 'thematicBreak':\n return 'thematicBreak';\n default:\n return 'text';\n }\n}\n\n/** Find a descendant node of a given type */\nfunction findNode(node: any, type: string): any {\n if (!node) {\n return null;\n }\n if (node.type === type) {\n return node;\n }\n if (Array.isArray(node.children)) {\n for (const child of node.children) {\n const found = findNode(child, type);\n if (found) {\n return found;\n }\n }\n }\n return null;\n}\n\nfunction extractTableCellLabel(node: any): string {\n if (!node) {\n return '';\n }\n if (node.type === 'image') {\n const parsed =\n typeof node.url === 'string' ? parseWeb5Url(node.url) : null;\n return parsed?.type === Web5UrlType.ICON ? '' : node.alt || '';\n }\n if (typeof node.value === 'string') {\n return node.value;\n }\n if (Array.isArray(node.children)) {\n return node.children.map(extractTableCellLabel).join('');\n }\n return '';\n}\n\nfunction tableCellMeta(cell: any): {\n label: string;\n href?: string;\n entityType?: string;\n entityId?: string;\n iconHint?: string;\n} {\n const label = extractTableCellLabel(cell).trim();\n const link = findNode(cell, 'link');\n const href = typeof link?.url === 'string' ? link.url : undefined;\n const parsed = href ? parseWeb5Url(href) : null;\n const image = findNode(cell, 'image');\n const imageUrl = typeof image?.url === 'string' ? image.url : '';\n const iconParsed = imageUrl ? parseWeb5Url(imageUrl) : null;\n\n return {\n label,\n ...(href ? { href } : {}),\n ...(parsed?.type === Web5UrlType.ENTITY\n ? {\n entityType: parsed.entityType,\n entityId: parsed.entityId,\n }\n : {}),\n ...(iconParsed?.type === Web5UrlType.ICON\n ? { iconHint: iconParsed.path }\n : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Source resolution — maps each enriched block back to its specific AST child\n// ---------------------------------------------------------------------------\n\ninterface ResolvedSource {\n content: string;\n markdown?: string;\n node: any;\n}\n\n/**\n * For non-paragraph nodes the whole AST node is the source.\n * For paragraphs, walk children with a cursor so that each block\n * (link, image, text-run) resolves to its own specific child(ren).\n */\nfunction resolveSource(\n block: EnrichedBlockElement,\n astNode: RootContent,\n cursors: Map<number, number>,\n): ResolvedSource {\n const n = astNode as any;\n\n if (n.type !== 'paragraph') {\n const content = extractContent(astNode);\n const md = extractContentMarkdown(astNode);\n return { content, markdown: md !== content ? md : undefined, node: n };\n }\n\n const children: any[] = n.children || [];\n const cursor = cursors.get(block.astNodeIndex) || 0;\n\n if (\n block.element === 'link' ||\n block.element === 'image' ||\n block.element === 'icon'\n ) {\n const target = block.element === 'icon' ? 'image' : block.element;\n for (let i = cursor; i < children.length; i++) {\n const found =\n children[i].type === target\n ? children[i]\n : findNode(children[i], target);\n if (found) {\n cursors.set(block.astNodeIndex, i + 1);\n return { content: extractContent(found), node: found };\n }\n }\n return { content: '', node: n };\n }\n\n if (block.element === 't') {\n let text = '';\n let md = '';\n let i = cursor;\n for (; i < children.length; i++) {\n const child = children[i];\n if (child.type === 'link' || child.type === 'image') {\n break;\n }\n // Also stop at formatting wrappers that contain links/images\n if (findNode(child, 'link') || findNode(child, 'image')) {\n break;\n }\n text += extractContent(child);\n md += extractContentMarkdown(child);\n }\n cursors.set(block.astNodeIndex, i);\n // Preserve markdown when it differs from plain text (has inline formatting)\n return { content: text, markdown: md !== text ? md : undefined, node: n };\n }\n\n return { content: extractContent(astNode), node: n };\n}\n\n// ---------------------------------------------------------------------------\n// Meta extraction — uses the resolved source node, not the whole parent\n// ---------------------------------------------------------------------------\n\n/**\n * Decompose list items into ListItemParts, each carrying nested Part[] in meta.\n * Reuses nodesToParts to resolve each item's inline content (links, images, text, etc.).\n */\nfunction buildListItems(listNode: any): Part[] {\n return (listNode.children || []).map((item: any) => {\n const itemParts = nodesToParts(item.children || []);\n\n // A \"complex\" item has more than plain text (e.g. links, images, mixed\n // inline elements). In that case content is meaningless — the real data\n // lives inside the nested parts.\n const isComplex =\n itemParts.length > 1 ||\n (itemParts.length === 1 && itemParts[0].type !== 'text');\n\n const part: Part = {\n type: 'listItem',\n role: 'list-item',\n content: isComplex ? '' : extractContent(item).trim(),\n };\n if (itemParts.length > 0) {\n const markdown = extractContentMarkdown(item).trim();\n part.meta = {\n parts: itemParts,\n // Always store markdown so consumers can access the original formatting\n // (e.g. **bold** titles). For simple items, content already has plain\n // text; markdown preserves inline formatting.\n ...(markdown ? { markdown } : {}),\n };\n }\n return part;\n });\n}\n\nfunction buildMeta(\n type: PartType,\n sourceNode: any,\n block: EnrichedBlockElement,\n): Record<string, unknown> | undefined {\n if (!sourceNode) {\n return undefined;\n }\n switch (type) {\n case 'heading':\n return { level: sourceNode.depth };\n case 'link': {\n const link =\n sourceNode.type === 'link' ? sourceNode : findNode(sourceNode, 'link');\n const lm = block.linkMetadata;\n return {\n href: lm?.url || link?.url || '',\n ...(link?.title ? { title: link.title } : {}),\n ...(lm?.entityType\n ? { isEntity: true, entityType: lm.entityType }\n : {}),\n };\n }\n case 'image': {\n const img =\n sourceNode.type === 'image'\n ? sourceNode\n : findNode(sourceNode, 'image');\n return img\n ? { src: img.url || '', ...(img.alt ? { alt: img.alt } : {}) }\n : undefined;\n }\n case 'icon': {\n const iconImg =\n sourceNode.type === 'image'\n ? sourceNode\n : findNode(sourceNode, 'image');\n const iconUrl: string = iconImg?.url || '';\n const web5Match = iconUrl.match(/^web5:\\/\\/icon\\/(.+)/);\n const iconHint = web5Match?.[1] || '';\n return iconHint ? { iconHint } : undefined;\n }\n case 'list': {\n const items = buildListItems(sourceNode);\n return { ordered: !!sourceNode.ordered, items };\n }\n case 'table': {\n const rows: any[] = sourceNode.children || [];\n if (rows.length === 0) {\n return undefined;\n }\n const headerRow = rows[0];\n const headerCells: ReturnType<typeof tableCellMeta>[] = (\n headerRow?.children || []\n ).map((cell: any) => tableCellMeta(cell));\n const columnLabels = headerCells.map((cell) => cell.label);\n const featureColumnLabel = columnLabels[0] || 'Feature';\n const dataRows = rows.slice(1).map((row: any) => {\n const cells = row.children || [];\n const firstCell = tableCellMeta(cells[0]);\n const values = cells\n .slice(1)\n .map((cell: any) => extractContent(cell).trim());\n return {\n feature: firstCell.label || '',\n ...(firstCell.iconHint ? { iconHint: firstCell.iconHint } : {}),\n values,\n };\n });\n return {\n columnLabels: columnLabels.slice(1),\n columns: headerCells.slice(1),\n featureColumnLabel,\n rows: dataRows,\n };\n }\n case 'callout': {\n const raw = extractContent(sourceNode).trim();\n const { kind } = parseCalloutTag(raw);\n return kind ? { kind } : undefined;\n }\n case 'htmlComment':\n return block.htmlCommentMetadata\n ? { ...block.htmlCommentMetadata }\n : undefined;\n default:\n return undefined;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Convert markdown AST nodes into a flat array of `Part` objects.\n *\n * Each enriched block element becomes one `Part`. When a single AST node\n * (e.g. a paragraph) produces multiple blocks (link + image + text), each\n * block becomes a separate Part with content resolved from its specific\n * child within the paragraph.\n *\n * @param nodes - Markdown AST nodes (typically a slice of `root.children`)\n * @returns Ordered array of Part objects\n */\nexport function nodesToParts(nodes: RootContent[]): Part[] {\n if (!nodes || nodes.length === 0) {\n return [];\n }\n\n const enrichedBlocks = convertToBlockElements(nodes);\n const parts: Part[] = [];\n const typeCounts: Record<string, number> = {};\n const childCursors = new Map<number, number>();\n\n for (const block of enrichedBlocks) {\n const astNode = nodes[block.astNodeIndex];\n const type = blockElementToPartType(block.element);\n const count = (typeCounts[type] || 0) + 1;\n typeCounts[type] = count;\n\n const source = resolveSource(block, astNode, childCursors);\n\n const part: Part = {\n type,\n role: deriveRole(type, count),\n content: source.content.trim(),\n };\n const meta = buildMeta(type, source.node, block);\n if (meta) {\n part.meta = meta;\n }\n\n // Preserve original markdown when inline formatting exists (bold, italic, etc.)\n if (source.markdown) {\n part.meta = { ...part.meta, markdown: source.markdown.trim() };\n }\n\n // For lists whose items are all complex, the list-level content is\n // meaningless (just a concatenation of child text). Clear it so that\n // consumers rely on the structured items in meta instead.\n if (type === 'list' && meta) {\n const items = (meta.items as Part[]) || [];\n const allComplex =\n items.length > 0 && items.every((it) => it.content === '');\n if (allComplex) {\n part.content = '';\n }\n }\n\n parts.push(part);\n }\n\n return parts;\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_markdownBlocks","require","_parts","_parseUtils","_entityLinkParser","_linkTypes","blockElementToPartType","element","startsWith","findNode","node","type","Array","isArray","children","child","found","extractTableCellLabel","parsed","url","parseWeb5Url","Web5UrlType","ICON","alt","value","map","join","tableCellMeta","cell","label","trim","link","href","undefined","image","imageUrl","iconParsed","ENTITY","entityType","entityId","iconHint","path","resolveSource","block","astNode","cursors","n","content","extractContent","md","extractContentMarkdown","markdown","cursor","get","astNodeIndex","target","i","length","set","text","buildListItems","listNode","item","itemParts","nodesToParts","isComplex","part","role","meta","parts","buildMeta","sourceNode","level","depth","lm","linkMetadata","title","isEntity","img","src","iconImg","iconUrl","web5Match","match","items","ordered","rows","headerRow","headerCells","columnLabels","featureColumnLabel","dataRows","slice","row","cells","firstCell","values","feature","columns","raw","kind","parseCalloutTag","htmlCommentMetadata","nodes","enrichedBlocks","convertToBlockElements","typeCounts","childCursors","Map","seenNodes","Set","count","source","deriveRole","has","continuesNode","add","allComplex","every","it","push"],"sources":["../../../src/match/astToParts.ts"],"sourcesContent":["import type { RootContent } from 'mdast';\nimport { convertToBlockElements } from '../markdownBlocks';\nimport type {\n EnrichedBlockElement,\n BlockElement,\n} from '../patternValidator/types';\nimport {\n type Part,\n type PartType,\n extractContent,\n extractContentMarkdown,\n deriveRole,\n} from '../parts/parts';\nimport { parseCalloutTag } from '../component/componentDefinitions/parse-utils';\nimport { parseWeb5Url } from '../utils/entityLinkParser';\nimport { Web5UrlType } from '../types/link-types';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction blockElementToPartType(element: BlockElement | string): PartType {\n if (typeof element === 'string' && element.startsWith('list{')) {\n return 'list';\n }\n switch (element) {\n case 'h1':\n case 'h2':\n case 'h3':\n case 'h4':\n case 'h5':\n case 'h6':\n return 'heading';\n case 't':\n return 'text';\n case 'link':\n return 'link';\n case 'image':\n return 'image';\n case 'icon':\n return 'icon';\n case 'list':\n return 'list';\n case 'table':\n return 'table';\n case 'blockquote':\n return 'blockquote';\n case 'callout':\n return 'callout';\n case 'html_comment':\n return 'htmlComment';\n case 'thematicBreak':\n return 'thematicBreak';\n default:\n return 'text';\n }\n}\n\n/** Find a descendant node of a given type */\nfunction findNode(node: any, type: string): any {\n if (!node) {\n return null;\n }\n if (node.type === type) {\n return node;\n }\n if (Array.isArray(node.children)) {\n for (const child of node.children) {\n const found = findNode(child, type);\n if (found) {\n return found;\n }\n }\n }\n return null;\n}\n\nfunction extractTableCellLabel(node: any): string {\n if (!node) {\n return '';\n }\n if (node.type === 'image') {\n const parsed =\n typeof node.url === 'string' ? parseWeb5Url(node.url) : null;\n return parsed?.type === Web5UrlType.ICON ? '' : node.alt || '';\n }\n if (typeof node.value === 'string') {\n return node.value;\n }\n if (Array.isArray(node.children)) {\n return node.children.map(extractTableCellLabel).join('');\n }\n return '';\n}\n\nfunction tableCellMeta(cell: any): {\n label: string;\n href?: string;\n entityType?: string;\n entityId?: string;\n iconHint?: string;\n} {\n const label = extractTableCellLabel(cell).trim();\n const link = findNode(cell, 'link');\n const href = typeof link?.url === 'string' ? link.url : undefined;\n const parsed = href ? parseWeb5Url(href) : null;\n const image = findNode(cell, 'image');\n const imageUrl = typeof image?.url === 'string' ? image.url : '';\n const iconParsed = imageUrl ? parseWeb5Url(imageUrl) : null;\n\n return {\n label,\n ...(href ? { href } : {}),\n ...(parsed?.type === Web5UrlType.ENTITY\n ? {\n entityType: parsed.entityType,\n entityId: parsed.entityId,\n }\n : {}),\n ...(iconParsed?.type === Web5UrlType.ICON\n ? { iconHint: iconParsed.path }\n : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Source resolution — maps each enriched block back to its specific AST child\n// ---------------------------------------------------------------------------\n\ninterface ResolvedSource {\n content: string;\n markdown?: string;\n node: any;\n}\n\n/**\n * For non-paragraph nodes the whole AST node is the source.\n * For paragraphs, walk children with a cursor so that each block\n * (link, image, text-run) resolves to its own specific child(ren).\n */\nfunction resolveSource(\n block: EnrichedBlockElement,\n astNode: RootContent,\n cursors: Map<number, number>,\n): ResolvedSource {\n const n = astNode as any;\n\n if (n.type !== 'paragraph') {\n const content = extractContent(astNode);\n const md = extractContentMarkdown(astNode);\n return { content, markdown: md !== content ? md : undefined, node: n };\n }\n\n const children: any[] = n.children || [];\n const cursor = cursors.get(block.astNodeIndex) || 0;\n\n if (\n block.element === 'link' ||\n block.element === 'image' ||\n block.element === 'icon'\n ) {\n const target = block.element === 'icon' ? 'image' : block.element;\n for (let i = cursor; i < children.length; i++) {\n const found =\n children[i].type === target\n ? children[i]\n : findNode(children[i], target);\n if (found) {\n cursors.set(block.astNodeIndex, i + 1);\n return { content: extractContent(found), node: found };\n }\n }\n return { content: '', node: n };\n }\n\n if (block.element === 't') {\n let text = '';\n let md = '';\n let i = cursor;\n for (; i < children.length; i++) {\n const child = children[i];\n if (child.type === 'link' || child.type === 'image') {\n break;\n }\n // Also stop at formatting wrappers that contain links/images\n if (findNode(child, 'link') || findNode(child, 'image')) {\n break;\n }\n text += extractContent(child);\n md += extractContentMarkdown(child);\n }\n cursors.set(block.astNodeIndex, i);\n // Preserve markdown when it differs from plain text (has inline formatting)\n return { content: text, markdown: md !== text ? md : undefined, node: n };\n }\n\n return { content: extractContent(astNode), node: n };\n}\n\n// ---------------------------------------------------------------------------\n// Meta extraction — uses the resolved source node, not the whole parent\n// ---------------------------------------------------------------------------\n\n/**\n * Decompose list items into ListItemParts, each carrying nested Part[] in meta.\n * Reuses nodesToParts to resolve each item's inline content (links, images, text, etc.).\n */\nfunction buildListItems(listNode: any): Part[] {\n return (listNode.children || []).map((item: any) => {\n const itemParts = nodesToParts(item.children || []);\n\n // A \"complex\" item has more than plain text (e.g. links, images, mixed\n // inline elements). In that case content is meaningless — the real data\n // lives inside the nested parts.\n const isComplex =\n itemParts.length > 1 ||\n (itemParts.length === 1 && itemParts[0].type !== 'text');\n\n const part: Part = {\n type: 'listItem',\n role: 'list-item',\n content: isComplex ? '' : extractContent(item).trim(),\n };\n if (itemParts.length > 0) {\n const markdown = extractContentMarkdown(item).trim();\n part.meta = {\n parts: itemParts,\n // Always store markdown so consumers can access the original formatting\n // (e.g. **bold** titles). For simple items, content already has plain\n // text; markdown preserves inline formatting.\n ...(markdown ? { markdown } : {}),\n };\n }\n return part;\n });\n}\n\nfunction buildMeta(\n type: PartType,\n sourceNode: any,\n block: EnrichedBlockElement,\n): Record<string, unknown> | undefined {\n if (!sourceNode) {\n return undefined;\n }\n switch (type) {\n case 'heading':\n return { level: sourceNode.depth };\n case 'link': {\n const link =\n sourceNode.type === 'link' ? sourceNode : findNode(sourceNode, 'link');\n const lm = block.linkMetadata;\n return {\n href: lm?.url || link?.url || '',\n ...(link?.title ? { title: link.title } : {}),\n ...(lm?.entityType\n ? { isEntity: true, entityType: lm.entityType }\n : {}),\n };\n }\n case 'image': {\n const img =\n sourceNode.type === 'image'\n ? sourceNode\n : findNode(sourceNode, 'image');\n return img\n ? { src: img.url || '', ...(img.alt ? { alt: img.alt } : {}) }\n : undefined;\n }\n case 'icon': {\n const iconImg =\n sourceNode.type === 'image'\n ? sourceNode\n : findNode(sourceNode, 'image');\n const iconUrl: string = iconImg?.url || '';\n const web5Match = iconUrl.match(/^web5:\\/\\/icon\\/(.+)/);\n const iconHint = web5Match?.[1] || '';\n return iconHint ? { iconHint } : undefined;\n }\n case 'list': {\n const items = buildListItems(sourceNode);\n return { ordered: !!sourceNode.ordered, items };\n }\n case 'table': {\n const rows: any[] = sourceNode.children || [];\n if (rows.length === 0) {\n return undefined;\n }\n const headerRow = rows[0];\n const headerCells: ReturnType<typeof tableCellMeta>[] = (\n headerRow?.children || []\n ).map((cell: any) => tableCellMeta(cell));\n const columnLabels = headerCells.map((cell) => cell.label);\n const featureColumnLabel = columnLabels[0] || 'Feature';\n const dataRows = rows.slice(1).map((row: any) => {\n const cells = row.children || [];\n const firstCell = tableCellMeta(cells[0]);\n const values = cells\n .slice(1)\n .map((cell: any) => extractContent(cell).trim());\n return {\n feature: firstCell.label || '',\n ...(firstCell.iconHint ? { iconHint: firstCell.iconHint } : {}),\n values,\n };\n });\n return {\n columnLabels: columnLabels.slice(1),\n columns: headerCells.slice(1),\n featureColumnLabel,\n rows: dataRows,\n };\n }\n case 'callout': {\n const raw = extractContent(sourceNode).trim();\n const { kind } = parseCalloutTag(raw);\n return kind ? { kind } : undefined;\n }\n case 'htmlComment':\n return block.htmlCommentMetadata\n ? { ...block.htmlCommentMetadata }\n : undefined;\n default:\n return undefined;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Convert markdown AST nodes into a flat array of `Part` objects.\n *\n * Each enriched block element becomes one `Part`. When a single AST node\n * (e.g. a paragraph) produces multiple blocks (link + image + text), each\n * block becomes a separate Part with content resolved from its specific\n * child within the paragraph.\n *\n * @param nodes - Markdown AST nodes (typically a slice of `root.children`)\n * @returns Ordered array of Part objects\n */\nexport function nodesToParts(nodes: RootContent[]): Part[] {\n if (!nodes || nodes.length === 0) {\n return [];\n }\n\n const enrichedBlocks = convertToBlockElements(nodes);\n const parts: Part[] = [];\n const typeCounts: Record<string, number> = {};\n const childCursors = new Map<number, number>();\n // A paragraph carrying an inline link is unwrapped into several block\n // elements (`t`, `link`, `t`), so its parts are siblings with nothing to say\n // they came from one paragraph. Mark every part after the first of a given\n // AST node, so a consumer rebuilding prose can tell \"same paragraph, keep\n // going\" from \"next paragraph, break\". Only split nodes carry the flag —\n // plain paragraphs, headings and images stay meta-free.\n const seenNodes = new Set<number>();\n\n for (const block of enrichedBlocks) {\n const astNode = nodes[block.astNodeIndex];\n const type = blockElementToPartType(block.element);\n const count = (typeCounts[type] || 0) + 1;\n typeCounts[type] = count;\n\n const source = resolveSource(block, astNode, childCursors);\n\n const part: Part = {\n type,\n role: deriveRole(type, count),\n content: source.content.trim(),\n };\n const meta = buildMeta(type, source.node, block);\n if (meta) {\n part.meta = meta;\n }\n\n // Preserve original markdown when inline formatting exists (bold, italic, etc.)\n if (source.markdown) {\n part.meta = { ...part.meta, markdown: source.markdown.trim() };\n }\n\n if (seenNodes.has(block.astNodeIndex)) {\n part.meta = { ...part.meta, continuesNode: true };\n }\n seenNodes.add(block.astNodeIndex);\n\n // For lists whose items are all complex, the list-level content is\n // meaningless (just a concatenation of child text). Clear it so that\n // consumers rely on the structured items in meta instead.\n if (type === 'list' && meta) {\n const items = (meta.items as Part[]) || [];\n const allComplex =\n items.length > 0 && items.every((it) => it.content === '');\n if (allComplex) {\n part.content = '';\n }\n }\n\n parts.push(part);\n }\n\n return parts;\n}\n"],"mappings":";;;;AACA,IAAAA,eAAA,GAAAC,OAAA;AAKA,IAAAC,MAAA,GAAAD,OAAA;AAOA,IAAAE,WAAA,GAAAF,OAAA;AACA,IAAAG,iBAAA,GAAAH,OAAA;AACA,IAAAI,UAAA,GAAAJ,OAAA;AAEA;AACA;AACA;;AAEA,SAASK,sBAAsBA,CAACC,OAA8B,EAAY;EACxE,IAAI,OAAOA,OAAO,KAAK,QAAQ,IAAIA,OAAO,CAACC,UAAU,CAAC,OAAO,CAAC,EAAE;IAC9D,OAAO,MAAM;EACf;EACA,QAAQD,OAAO;IACb,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;MACP,OAAO,SAAS;IAClB,KAAK,GAAG;MACN,OAAO,MAAM;IACf,KAAK,MAAM;MACT,OAAO,MAAM;IACf,KAAK,OAAO;MACV,OAAO,OAAO;IAChB,KAAK,MAAM;MACT,OAAO,MAAM;IACf,KAAK,MAAM;MACT,OAAO,MAAM;IACf,KAAK,OAAO;MACV,OAAO,OAAO;IAChB,KAAK,YAAY;MACf,OAAO,YAAY;IACrB,KAAK,SAAS;MACZ,OAAO,SAAS;IAClB,KAAK,cAAc;MACjB,OAAO,aAAa;IACtB,KAAK,eAAe;MAClB,OAAO,eAAe;IACxB;MACE,OAAO,MAAM;EACjB;AACF;;AAEA;AACA,SAASE,QAAQA,CAACC,IAAS,EAAEC,IAAY,EAAO;EAC9C,IAAI,CAACD,IAAI,EAAE;IACT,OAAO,IAAI;EACb;EACA,IAAIA,IAAI,CAACC,IAAI,KAAKA,IAAI,EAAE;IACtB,OAAOD,IAAI;EACb;EACA,IAAIE,KAAK,CAACC,OAAO,CAACH,IAAI,CAACI,QAAQ,CAAC,EAAE;IAChC,KAAK,MAAMC,KAAK,IAAIL,IAAI,CAACI,QAAQ,EAAE;MACjC,MAAME,KAAK,GAAGP,QAAQ,CAACM,KAAK,EAAEJ,IAAI,CAAC;MACnC,IAAIK,KAAK,EAAE;QACT,OAAOA,KAAK;MACd;IACF;EACF;EACA,OAAO,IAAI;AACb;AAEA,SAASC,qBAAqBA,CAACP,IAAS,EAAU;EAChD,IAAI,CAACA,IAAI,EAAE;IACT,OAAO,EAAE;EACX;EACA,IAAIA,IAAI,CAACC,IAAI,KAAK,OAAO,EAAE;IACzB,MAAMO,MAAM,GACV,OAAOR,IAAI,CAACS,GAAG,KAAK,QAAQ,GAAG,IAAAC,8BAAY,EAACV,IAAI,CAACS,GAAG,CAAC,GAAG,IAAI;IAC9D,OAAO,CAAAD,MAAM,oBAANA,MAAM,CAAEP,IAAI,MAAKU,sBAAW,CAACC,IAAI,GAAG,EAAE,GAAGZ,IAAI,CAACa,GAAG,IAAI,EAAE;EAChE;EACA,IAAI,OAAOb,IAAI,CAACc,KAAK,KAAK,QAAQ,EAAE;IAClC,OAAOd,IAAI,CAACc,KAAK;EACnB;EACA,IAAIZ,KAAK,CAACC,OAAO,CAACH,IAAI,CAACI,QAAQ,CAAC,EAAE;IAChC,OAAOJ,IAAI,CAACI,QAAQ,CAACW,GAAG,CAACR,qBAAqB,CAAC,CAACS,IAAI,CAAC,EAAE,CAAC;EAC1D;EACA,OAAO,EAAE;AACX;AAEA,SAASC,aAAaA,CAACC,IAAS,EAM9B;EACA,MAAMC,KAAK,GAAGZ,qBAAqB,CAACW,IAAI,CAAC,CAACE,IAAI,CAAC,CAAC;EAChD,MAAMC,IAAI,GAAGtB,QAAQ,CAACmB,IAAI,EAAE,MAAM,CAAC;EACnC,MAAMI,IAAI,GAAG,QAAOD,IAAI,oBAAJA,IAAI,CAAEZ,GAAG,MAAK,QAAQ,GAAGY,IAAI,CAACZ,GAAG,GAAGc,SAAS;EACjE,MAAMf,MAAM,GAAGc,IAAI,GAAG,IAAAZ,8BAAY,EAACY,IAAI,CAAC,GAAG,IAAI;EAC/C,MAAME,KAAK,GAAGzB,QAAQ,CAACmB,IAAI,EAAE,OAAO,CAAC;EACrC,MAAMO,QAAQ,GAAG,QAAOD,KAAK,oBAALA,KAAK,CAAEf,GAAG,MAAK,QAAQ,GAAGe,KAAK,CAACf,GAAG,GAAG,EAAE;EAChE,MAAMiB,UAAU,GAAGD,QAAQ,GAAG,IAAAf,8BAAY,EAACe,QAAQ,CAAC,GAAG,IAAI;EAE3D,OAAO;IACLN,KAAK;IACL,IAAIG,IAAI,GAAG;MAAEA;IAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,IAAI,CAAAd,MAAM,oBAANA,MAAM,CAAEP,IAAI,MAAKU,sBAAW,CAACgB,MAAM,GACnC;MACEC,UAAU,EAAEpB,MAAM,CAACoB,UAAU;MAC7BC,QAAQ,EAAErB,MAAM,CAACqB;IACnB,CAAC,GACD,CAAC,CAAC,CAAC;IACP,IAAI,CAAAH,UAAU,oBAAVA,UAAU,CAAEzB,IAAI,MAAKU,sBAAW,CAACC,IAAI,GACrC;MAAEkB,QAAQ,EAAEJ,UAAU,CAACK;IAAK,CAAC,GAC7B,CAAC,CAAC;EACR,CAAC;AACH;;AAEA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA,SAASC,aAAaA,CACpBC,KAA2B,EAC3BC,OAAoB,EACpBC,OAA4B,EACZ;EAChB,MAAMC,CAAC,GAAGF,OAAc;EAExB,IAAIE,CAAC,CAACnC,IAAI,KAAK,WAAW,EAAE;IAC1B,MAAMoC,OAAO,GAAG,IAAAC,qBAAc,EAACJ,OAAO,CAAC;IACvC,MAAMK,EAAE,GAAG,IAAAC,6BAAsB,EAACN,OAAO,CAAC;IAC1C,OAAO;MAAEG,OAAO;MAAEI,QAAQ,EAAEF,EAAE,KAAKF,OAAO,GAAGE,EAAE,GAAGhB,SAAS;MAAEvB,IAAI,EAAEoC;IAAE,CAAC;EACxE;EAEA,MAAMhC,QAAe,GAAGgC,CAAC,CAAChC,QAAQ,IAAI,EAAE;EACxC,MAAMsC,MAAM,GAAGP,OAAO,CAACQ,GAAG,CAACV,KAAK,CAACW,YAAY,CAAC,IAAI,CAAC;EAEnD,IACEX,KAAK,CAACpC,OAAO,KAAK,MAAM,IACxBoC,KAAK,CAACpC,OAAO,KAAK,OAAO,IACzBoC,KAAK,CAACpC,OAAO,KAAK,MAAM,EACxB;IACA,MAAMgD,MAAM,GAAGZ,KAAK,CAACpC,OAAO,KAAK,MAAM,GAAG,OAAO,GAAGoC,KAAK,CAACpC,OAAO;IACjE,KAAK,IAAIiD,CAAC,GAAGJ,MAAM,EAAEI,CAAC,GAAG1C,QAAQ,CAAC2C,MAAM,EAAED,CAAC,EAAE,EAAE;MAC7C,MAAMxC,KAAK,GACTF,QAAQ,CAAC0C,CAAC,CAAC,CAAC7C,IAAI,KAAK4C,MAAM,GACvBzC,QAAQ,CAAC0C,CAAC,CAAC,GACX/C,QAAQ,CAACK,QAAQ,CAAC0C,CAAC,CAAC,EAAED,MAAM,CAAC;MACnC,IAAIvC,KAAK,EAAE;QACT6B,OAAO,CAACa,GAAG,CAACf,KAAK,CAACW,YAAY,EAAEE,CAAC,GAAG,CAAC,CAAC;QACtC,OAAO;UAAET,OAAO,EAAE,IAAAC,qBAAc,EAAChC,KAAK,CAAC;UAAEN,IAAI,EAAEM;QAAM,CAAC;MACxD;IACF;IACA,OAAO;MAAE+B,OAAO,EAAE,EAAE;MAAErC,IAAI,EAAEoC;IAAE,CAAC;EACjC;EAEA,IAAIH,KAAK,CAACpC,OAAO,KAAK,GAAG,EAAE;IACzB,IAAIoD,IAAI,GAAG,EAAE;IACb,IAAIV,EAAE,GAAG,EAAE;IACX,IAAIO,CAAC,GAAGJ,MAAM;IACd,OAAOI,CAAC,GAAG1C,QAAQ,CAAC2C,MAAM,EAAED,CAAC,EAAE,EAAE;MAC/B,MAAMzC,KAAK,GAAGD,QAAQ,CAAC0C,CAAC,CAAC;MACzB,IAAIzC,KAAK,CAACJ,IAAI,KAAK,MAAM,IAAII,KAAK,CAACJ,IAAI,KAAK,OAAO,EAAE;QACnD;MACF;MACA;MACA,IAAIF,QAAQ,CAACM,KAAK,EAAE,MAAM,CAAC,IAAIN,QAAQ,CAACM,KAAK,EAAE,OAAO,CAAC,EAAE;QACvD;MACF;MACA4C,IAAI,IAAI,IAAAX,qBAAc,EAACjC,KAAK,CAAC;MAC7BkC,EAAE,IAAI,IAAAC,6BAAsB,EAACnC,KAAK,CAAC;IACrC;IACA8B,OAAO,CAACa,GAAG,CAACf,KAAK,CAACW,YAAY,EAAEE,CAAC,CAAC;IAClC;IACA,OAAO;MAAET,OAAO,EAAEY,IAAI;MAAER,QAAQ,EAAEF,EAAE,KAAKU,IAAI,GAAGV,EAAE,GAAGhB,SAAS;MAAEvB,IAAI,EAAEoC;IAAE,CAAC;EAC3E;EAEA,OAAO;IAAEC,OAAO,EAAE,IAAAC,qBAAc,EAACJ,OAAO,CAAC;IAAElC,IAAI,EAAEoC;EAAE,CAAC;AACtD;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,SAASc,cAAcA,CAACC,QAAa,EAAU;EAC7C,OAAO,CAACA,QAAQ,CAAC/C,QAAQ,IAAI,EAAE,EAAEW,GAAG,CAAEqC,IAAS,IAAK;IAClD,MAAMC,SAAS,GAAGC,YAAY,CAACF,IAAI,CAAChD,QAAQ,IAAI,EAAE,CAAC;;IAEnD;IACA;IACA;IACA,MAAMmD,SAAS,GACbF,SAAS,CAACN,MAAM,GAAG,CAAC,IACnBM,SAAS,CAACN,MAAM,KAAK,CAAC,IAAIM,SAAS,CAAC,CAAC,CAAC,CAACpD,IAAI,KAAK,MAAO;IAE1D,MAAMuD,IAAU,GAAG;MACjBvD,IAAI,EAAE,UAAU;MAChBwD,IAAI,EAAE,WAAW;MACjBpB,OAAO,EAAEkB,SAAS,GAAG,EAAE,GAAG,IAAAjB,qBAAc,EAACc,IAAI,CAAC,CAAChC,IAAI,CAAC;IACtD,CAAC;IACD,IAAIiC,SAAS,CAACN,MAAM,GAAG,CAAC,EAAE;MACxB,MAAMN,QAAQ,GAAG,IAAAD,6BAAsB,EAACY,IAAI,CAAC,CAAChC,IAAI,CAAC,CAAC;MACpDoC,IAAI,CAACE,IAAI,GAAG;QACVC,KAAK,EAAEN,SAAS;QAChB;QACA;QACA;QACA,IAAIZ,QAAQ,GAAG;UAAEA;QAAS,CAAC,GAAG,CAAC,CAAC;MAClC,CAAC;IACH;IACA,OAAOe,IAAI;EACb,CAAC,CAAC;AACJ;AAEA,SAASI,SAASA,CAChB3D,IAAc,EACd4D,UAAe,EACf5B,KAA2B,EACU;EACrC,IAAI,CAAC4B,UAAU,EAAE;IACf,OAAOtC,SAAS;EAClB;EACA,QAAQtB,IAAI;IACV,KAAK,SAAS;MACZ,OAAO;QAAE6D,KAAK,EAAED,UAAU,CAACE;MAAM,CAAC;IACpC,KAAK,MAAM;MAAE;QACX,MAAM1C,IAAI,GACRwC,UAAU,CAAC5D,IAAI,KAAK,MAAM,GAAG4D,UAAU,GAAG9D,QAAQ,CAAC8D,UAAU,EAAE,MAAM,CAAC;QACxE,MAAMG,EAAE,GAAG/B,KAAK,CAACgC,YAAY;QAC7B,OAAO;UACL3C,IAAI,EAAE,CAAA0C,EAAE,oBAAFA,EAAE,CAAEvD,GAAG,MAAIY,IAAI,oBAAJA,IAAI,CAAEZ,GAAG,KAAI,EAAE;UAChC,IAAIY,IAAI,YAAJA,IAAI,CAAE6C,KAAK,GAAG;YAAEA,KAAK,EAAE7C,IAAI,CAAC6C;UAAM,CAAC,GAAG,CAAC,CAAC,CAAC;UAC7C,IAAIF,EAAE,YAAFA,EAAE,CAAEpC,UAAU,GACd;YAAEuC,QAAQ,EAAE,IAAI;YAAEvC,UAAU,EAAEoC,EAAE,CAACpC;UAAW,CAAC,GAC7C,CAAC,CAAC;QACR,CAAC;MACH;IACA,KAAK,OAAO;MAAE;QACZ,MAAMwC,GAAG,GACPP,UAAU,CAAC5D,IAAI,KAAK,OAAO,GACvB4D,UAAU,GACV9D,QAAQ,CAAC8D,UAAU,EAAE,OAAO,CAAC;QACnC,OAAOO,GAAG,GACN;UAAEC,GAAG,EAAED,GAAG,CAAC3D,GAAG,IAAI,EAAE;UAAE,IAAI2D,GAAG,CAACvD,GAAG,GAAG;YAAEA,GAAG,EAAEuD,GAAG,CAACvD;UAAI,CAAC,GAAG,CAAC,CAAC;QAAE,CAAC,GAC5DU,SAAS;MACf;IACA,KAAK,MAAM;MAAE;QACX,MAAM+C,OAAO,GACXT,UAAU,CAAC5D,IAAI,KAAK,OAAO,GACvB4D,UAAU,GACV9D,QAAQ,CAAC8D,UAAU,EAAE,OAAO,CAAC;QACnC,MAAMU,OAAe,GAAG,CAAAD,OAAO,oBAAPA,OAAO,CAAE7D,GAAG,KAAI,EAAE;QAC1C,MAAM+D,SAAS,GAAGD,OAAO,CAACE,KAAK,CAAC,sBAAsB,CAAC;QACvD,MAAM3C,QAAQ,GAAG,CAAA0C,SAAS,oBAATA,SAAS,CAAG,CAAC,CAAC,KAAI,EAAE;QACrC,OAAO1C,QAAQ,GAAG;UAAEA;QAAS,CAAC,GAAGP,SAAS;MAC5C;IACA,KAAK,MAAM;MAAE;QACX,MAAMmD,KAAK,GAAGxB,cAAc,CAACW,UAAU,CAAC;QACxC,OAAO;UAAEc,OAAO,EAAE,CAAC,CAACd,UAAU,CAACc,OAAO;UAAED;QAAM,CAAC;MACjD;IACA,KAAK,OAAO;MAAE;QACZ,MAAME,IAAW,GAAGf,UAAU,CAACzD,QAAQ,IAAI,EAAE;QAC7C,IAAIwE,IAAI,CAAC7B,MAAM,KAAK,CAAC,EAAE;UACrB,OAAOxB,SAAS;QAClB;QACA,MAAMsD,SAAS,GAAGD,IAAI,CAAC,CAAC,CAAC;QACzB,MAAME,WAA+C,GAAG,CACtD,CAAAD,SAAS,oBAATA,SAAS,CAAEzE,QAAQ,KAAI,EAAE,EACzBW,GAAG,CAAEG,IAAS,IAAKD,aAAa,CAACC,IAAI,CAAC,CAAC;QACzC,MAAM6D,YAAY,GAAGD,WAAW,CAAC/D,GAAG,CAAEG,IAAI,IAAKA,IAAI,CAACC,KAAK,CAAC;QAC1D,MAAM6D,kBAAkB,GAAGD,YAAY,CAAC,CAAC,CAAC,IAAI,SAAS;QACvD,MAAME,QAAQ,GAAGL,IAAI,CAACM,KAAK,CAAC,CAAC,CAAC,CAACnE,GAAG,CAAEoE,GAAQ,IAAK;UAC/C,MAAMC,KAAK,GAAGD,GAAG,CAAC/E,QAAQ,IAAI,EAAE;UAChC,MAAMiF,SAAS,GAAGpE,aAAa,CAACmE,KAAK,CAAC,CAAC,CAAC,CAAC;UACzC,MAAME,MAAM,GAAGF,KAAK,CACjBF,KAAK,CAAC,CAAC,CAAC,CACRnE,GAAG,CAAEG,IAAS,IAAK,IAAAoB,qBAAc,EAACpB,IAAI,CAAC,CAACE,IAAI,CAAC,CAAC,CAAC;UAClD,OAAO;YACLmE,OAAO,EAAEF,SAAS,CAAClE,KAAK,IAAI,EAAE;YAC9B,IAAIkE,SAAS,CAACvD,QAAQ,GAAG;cAAEA,QAAQ,EAAEuD,SAAS,CAACvD;YAAS,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/DwD;UACF,CAAC;QACH,CAAC,CAAC;QACF,OAAO;UACLP,YAAY,EAAEA,YAAY,CAACG,KAAK,CAAC,CAAC,CAAC;UACnCM,OAAO,EAAEV,WAAW,CAACI,KAAK,CAAC,CAAC,CAAC;UAC7BF,kBAAkB;UAClBJ,IAAI,EAAEK;QACR,CAAC;MACH;IACA,KAAK,SAAS;MAAE;QACd,MAAMQ,GAAG,GAAG,IAAAnD,qBAAc,EAACuB,UAAU,CAAC,CAACzC,IAAI,CAAC,CAAC;QAC7C,MAAM;UAAEsE;QAAK,CAAC,GAAG,IAAAC,2BAAe,EAACF,GAAG,CAAC;QACrC,OAAOC,IAAI,GAAG;UAAEA;QAAK,CAAC,GAAGnE,SAAS;MACpC;IACA,KAAK,aAAa;MAChB,OAAOU,KAAK,CAAC2D,mBAAmB,GAC5B;QAAE,GAAG3D,KAAK,CAAC2D;MAAoB,CAAC,GAChCrE,SAAS;IACf;MACE,OAAOA,SAAS;EACpB;AACF;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS+B,YAAYA,CAACuC,KAAoB,EAAU;EACzD,IAAI,CAACA,KAAK,IAAIA,KAAK,CAAC9C,MAAM,KAAK,CAAC,EAAE;IAChC,OAAO,EAAE;EACX;EAEA,MAAM+C,cAAc,GAAG,IAAAC,sCAAsB,EAACF,KAAK,CAAC;EACpD,MAAMlC,KAAa,GAAG,EAAE;EACxB,MAAMqC,UAAkC,GAAG,CAAC,CAAC;EAC7C,MAAMC,YAAY,GAAG,IAAIC,GAAG,CAAiB,CAAC;EAC9C;EACA;EACA;EACA;EACA;EACA;EACA,MAAMC,SAAS,GAAG,IAAIC,GAAG,CAAS,CAAC;EAEnC,KAAK,MAAMnE,KAAK,IAAI6D,cAAc,EAAE;IAClC,MAAM5D,OAAO,GAAG2D,KAAK,CAAC5D,KAAK,CAACW,YAAY,CAAC;IACzC,MAAM3C,IAAI,GAAGL,sBAAsB,CAACqC,KAAK,CAACpC,OAAO,CAAC;IAClD,MAAMwG,KAAK,GAAG,CAACL,UAAU,CAAC/F,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACzC+F,UAAU,CAAC/F,IAAI,CAAC,GAAGoG,KAAK;IAExB,MAAMC,MAAM,GAAGtE,aAAa,CAACC,KAAK,EAAEC,OAAO,EAAE+D,YAAY,CAAC;IAE1D,MAAMzC,IAAU,GAAG;MACjBvD,IAAI;MACJwD,IAAI,EAAE,IAAA8C,iBAAU,EAACtG,IAAI,EAAEoG,KAAK,CAAC;MAC7BhE,OAAO,EAAEiE,MAAM,CAACjE,OAAO,CAACjB,IAAI,CAAC;IAC/B,CAAC;IACD,MAAMsC,IAAI,GAAGE,SAAS,CAAC3D,IAAI,EAAEqG,MAAM,CAACtG,IAAI,EAAEiC,KAAK,CAAC;IAChD,IAAIyB,IAAI,EAAE;MACRF,IAAI,CAACE,IAAI,GAAGA,IAAI;IAClB;;IAEA;IACA,IAAI4C,MAAM,CAAC7D,QAAQ,EAAE;MACnBe,IAAI,CAACE,IAAI,GAAG;QAAE,GAAGF,IAAI,CAACE,IAAI;QAAEjB,QAAQ,EAAE6D,MAAM,CAAC7D,QAAQ,CAACrB,IAAI,CAAC;MAAE,CAAC;IAChE;IAEA,IAAI+E,SAAS,CAACK,GAAG,CAACvE,KAAK,CAACW,YAAY,CAAC,EAAE;MACrCY,IAAI,CAACE,IAAI,GAAG;QAAE,GAAGF,IAAI,CAACE,IAAI;QAAE+C,aAAa,EAAE;MAAK,CAAC;IACnD;IACAN,SAAS,CAACO,GAAG,CAACzE,KAAK,CAACW,YAAY,CAAC;;IAEjC;IACA;IACA;IACA,IAAI3C,IAAI,KAAK,MAAM,IAAIyD,IAAI,EAAE;MAC3B,MAAMgB,KAAK,GAAIhB,IAAI,CAACgB,KAAK,IAAe,EAAE;MAC1C,MAAMiC,UAAU,GACdjC,KAAK,CAAC3B,MAAM,GAAG,CAAC,IAAI2B,KAAK,CAACkC,KAAK,CAAEC,EAAE,IAAKA,EAAE,CAACxE,OAAO,KAAK,EAAE,CAAC;MAC5D,IAAIsE,UAAU,EAAE;QACdnD,IAAI,CAACnB,OAAO,GAAG,EAAE;MACnB;IACF;IAEAsB,KAAK,CAACmD,IAAI,CAACtD,IAAI,CAAC;EAClB;EAEA,OAAOG,KAAK;AACd","ignoreList":[]}
@@ -87,9 +87,16 @@ function transformShopifyProduct(product, variantId) {
87
87
  var _product$featuredImag, _product$options;
88
88
  const variants = extractVariants(product);
89
89
  // The turn decided WHICH variant to show; the live fetch is here for what is
90
- // true about it right now. With no matched variant — or one the store has
91
- // since dropped — the product-level range is still the only answer we have.
90
+ // true about it right now.
92
91
  const matchedVariant = findLiveVariant(variants, variantId);
92
+ // Whether this fetch is answering about the whole product or about one
93
+ // child of it. A row that named a child but whose child the store no longer
94
+ // returns — dropped since the turn, or past the `variants(first: 100)` page
95
+ // — is NOT a whole-product row: answering it with the product's floor would
96
+ // put a different variant's price under this variant's name, which is the
97
+ // bug the row's own `variantId` exists to prevent. So the live price is
98
+ // simply withheld and the turn's, which was right about this child, stands.
99
+ const pricesWholeProduct = !variantId;
93
100
  return {
94
101
  url: product.onlineStoreUrl || '',
95
102
  title: product.title,
@@ -98,11 +105,16 @@ function transformShopifyProduct(product, variantId) {
98
105
  imageUrl: (_product$featuredImag = product.featuredImage) == null ? void 0 : _product$featuredImag.url,
99
106
  secondaryImageUrl: getSecondaryImage(product),
100
107
  vendor: product.vendor,
101
- price: (matchedVariant == null ? void 0 : matchedVariant.price) || formatPrice(product.priceRange.minVariantPrice),
102
- // Keyed on the variant, not on its value: an unmatched product falls back
108
+ price: (matchedVariant == null ? void 0 : matchedVariant.price) ?? (pricesWholeProduct ? formatPrice(product.priceRange.minVariantPrice) : undefined),
109
+ // The span the whole product covers, in the store's live currency. A card
110
+ // reads it to decide whether "from" is honest; it is the product's either
111
+ // way, so it is reported whether or not a child matched.
112
+ priceMin: formatPrice(product.priceRange.minVariantPrice),
113
+ priceMax: formatPrice(product.priceRange.maxVariantPrice),
114
+ // Keyed on the variant, not on its value: a whole-product row falls back
103
115
  // to the product-level compare-at, but a matched variant that is simply
104
116
  // not on sale must not inherit one and print a false strikethrough.
105
- compareAtPrice: matchedVariant ? matchedVariant.compareAtPrice : getCompareAtPrice(product),
117
+ compareAtPrice: matchedVariant ? matchedVariant.compareAtPrice : pricesWholeProduct ? getCompareAtPrice(product) : undefined,
106
118
  available: matchedVariant == null ? void 0 : matchedVariant.available,
107
119
  sizes: extractSizes(product),
108
120
  variants,
@@ -1 +1 @@
1
- {"version":3,"names":["formatShopifyDate","isoDate","date","Date","isNaN","getTime","undefined","day","getDate","month","toLocaleString","year","getFullYear","formatPrice","money","amount","parseFloat","formatted","toFixed","currencyCode","extractSizes","product","sizes","variants","edges","flatMap","e","node","selectedOptions","filter","o","name","toLowerCase","map","value","unique","Set","length","extractVariants","sizeOption","find","numericId","id","replace","label","title","variantId","available","availableForSale","price","compareAtPrice","getSecondaryImage","images","url","getAllImages","getCompareAtPrice","_product$compareAtPri","compare","compareAtPriceRange","minVariantPrice","min","priceRange","findLiveVariant","variant","transformShopifyProduct","_product$featuredImag","_product$options","matchedVariant","onlineStoreUrl","type","description","imageUrl","featuredImage","secondaryImageUrl","vendor","allImages","options","tags","transformShopifyCollection","collection","_collection$image","image","transformShopifyArticle","article","_article$image","_article$authorV","excerpt","publishDate","publishedAt","author","authorV2","transformShopifyEntityToItemData","entity","entityType"],"sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"sourcesContent":["import type { EntityItemData } from '../../component/types';\nimport type {\n ShopifyProduct,\n ShopifyCollection,\n ShopifyArticle,\n} from './types';\n\nfunction formatShopifyDate(isoDate: string): string | undefined {\n const date = new Date(isoDate);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const day = date.getDate();\n const month = date.toLocaleString('en-US', { month: 'short' });\n const year = date.getFullYear();\n return `${day} ${month} ${year}`;\n}\n\nfunction formatPrice(\n money: ShopifyProduct['priceRange']['minVariantPrice'],\n): string {\n const amount = parseFloat(money.amount);\n const formatted = amount.toFixed(2);\n return `${formatted} ${money.currencyCode}`;\n}\n\nfunction extractSizes(product: ShopifyProduct): string[] | undefined {\n const sizes = product.variants.edges.flatMap((e) =>\n e.node.selectedOptions\n .filter((o) => o.name.toLowerCase() === 'size')\n .map((o) => o.value),\n );\n const unique = [...new Set(sizes)];\n return unique.length > 0 ? unique : undefined;\n}\n\n/**\n * Extract variant ID → size label mapping for add-to-cart.\n * Shopify global IDs look like \"gid://shopify/ProductVariant/12345\" —\n * we extract just the numeric part for the Ajax API.\n */\nfunction extractVariants(product: ShopifyProduct): EntityItemData['variants'] {\n const variants = product.variants.edges.map((e) => {\n const sizeOption = e.node.selectedOptions.find(\n (o) => o.name.toLowerCase() === 'size',\n );\n const numericId = e.node.id.replace(\n /^gid:\\/\\/shopify\\/ProductVariant\\//,\n '',\n );\n return {\n label: sizeOption?.value || e.node.title,\n variantId: numericId,\n available: e.node.availableForSale,\n selectedOptions: e.node.selectedOptions.map((o) => ({\n name: o.name,\n value: o.value,\n })),\n price: formatPrice(e.node.price),\n compareAtPrice: e.node.compareAtPrice\n ? formatPrice(e.node.compareAtPrice)\n : undefined,\n };\n });\n return variants.length > 0 ? variants : undefined;\n}\n\nfunction getSecondaryImage(product: ShopifyProduct): string | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 1 ? images[1] : undefined;\n}\n\nfunction getAllImages(product: ShopifyProduct): string[] | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 0 ? images : undefined;\n}\n\nfunction getCompareAtPrice(product: ShopifyProduct): string | undefined {\n const compare = product.compareAtPriceRange?.minVariantPrice;\n if (!compare) {\n return undefined;\n }\n const min = product.priceRange.minVariantPrice;\n if (parseFloat(compare.amount) <= parseFloat(min.amount)) {\n return undefined;\n }\n return formatPrice(compare);\n}\n\n/**\n * The live entry for the variant the turn resolved, if the store still has it.\n * Both sides carry the bare numeric id — `extractVariants` strips the\n * `gid://shopify/ProductVariant/` prefix, and the turn never adds one.\n */\nfunction findLiveVariant(\n variants: EntityItemData['variants'],\n variantId: string | undefined,\n): NonNullable<EntityItemData['variants']>[number] | undefined {\n if (!variantId) {\n return undefined;\n }\n return variants?.find((variant) => variant.variantId === variantId);\n}\n\nexport function transformShopifyProduct(\n product: ShopifyProduct,\n variantId?: string,\n): EntityItemData {\n const variants = extractVariants(product);\n // The turn decided WHICH variant to show; the live fetch is here for what is\n // true about it right now. With no matched variant — or one the store has\n // since dropped — the product-level range is still the only answer we have.\n const matchedVariant = findLiveVariant(variants, variantId);\n\n return {\n url: product.onlineStoreUrl || '',\n title: product.title,\n type: 'product',\n description: product.description,\n imageUrl: product.featuredImage?.url,\n secondaryImageUrl: getSecondaryImage(product),\n vendor: product.vendor,\n price:\n matchedVariant?.price || formatPrice(product.priceRange.minVariantPrice),\n // Keyed on the variant, not on its value: an unmatched product falls back\n // to the product-level compare-at, but a matched variant that is simply\n // not on sale must not inherit one and print a false strikethrough.\n compareAtPrice: matchedVariant\n ? matchedVariant.compareAtPrice\n : getCompareAtPrice(product),\n available: matchedVariant?.available,\n sizes: extractSizes(product),\n variants,\n allImages: getAllImages(product),\n options: product.options?.length ? product.options : undefined,\n tags: product.tags.length ? product.tags : undefined,\n };\n}\n\nexport function transformShopifyCollection(\n collection: ShopifyCollection,\n): EntityItemData {\n return {\n url: collection.onlineStoreUrl || '',\n title: collection.title,\n type: 'collection',\n description: collection.description,\n imageUrl: collection.image?.url,\n };\n}\n\nexport function transformShopifyArticle(\n article: ShopifyArticle,\n): EntityItemData {\n return {\n url: article.onlineStoreUrl || '',\n title: article.title,\n type: 'article',\n description: article.excerpt || undefined,\n imageUrl: article.image?.url,\n publishDate: formatShopifyDate(article.publishedAt),\n author: article.authorV2?.name || undefined,\n tags: article.tags.length ? article.tags : undefined,\n };\n}\n\n/**\n * Transform any resolved Shopify entity to the standard EntityItemData shape.\n * Detects the entity type by checking for type-specific fields.\n */\nexport function transformShopifyEntityToItemData(\n entity: ShopifyProduct | ShopifyCollection | ShopifyArticle,\n entityType: string,\n variantId?: string,\n): EntityItemData {\n switch (entityType.toLowerCase()) {\n case 'product':\n return transformShopifyProduct(entity as ShopifyProduct, variantId);\n case 'collection':\n return transformShopifyCollection(entity as ShopifyCollection);\n case 'article':\n return transformShopifyArticle(entity as ShopifyArticle);\n default:\n return {\n url: '',\n title: (entity as { title?: string }).title || '',\n type: entityType,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["formatShopifyDate","isoDate","date","Date","isNaN","getTime","undefined","day","getDate","month","toLocaleString","year","getFullYear","formatPrice","money","amount","parseFloat","formatted","toFixed","currencyCode","extractSizes","product","sizes","variants","edges","flatMap","e","node","selectedOptions","filter","o","name","toLowerCase","map","value","unique","Set","length","extractVariants","sizeOption","find","numericId","id","replace","label","title","variantId","available","availableForSale","price","compareAtPrice","getSecondaryImage","images","url","getAllImages","getCompareAtPrice","_product$compareAtPri","compare","compareAtPriceRange","minVariantPrice","min","priceRange","findLiveVariant","variant","transformShopifyProduct","_product$featuredImag","_product$options","matchedVariant","pricesWholeProduct","onlineStoreUrl","type","description","imageUrl","featuredImage","secondaryImageUrl","vendor","priceMin","priceMax","maxVariantPrice","allImages","options","tags","transformShopifyCollection","collection","_collection$image","image","transformShopifyArticle","article","_article$image","_article$authorV","excerpt","publishDate","publishedAt","author","authorV2","transformShopifyEntityToItemData","entity","entityType"],"sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"sourcesContent":["import type { EntityItemData } from '../../component/types';\nimport type {\n ShopifyProduct,\n ShopifyCollection,\n ShopifyArticle,\n} from './types';\n\nfunction formatShopifyDate(isoDate: string): string | undefined {\n const date = new Date(isoDate);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const day = date.getDate();\n const month = date.toLocaleString('en-US', { month: 'short' });\n const year = date.getFullYear();\n return `${day} ${month} ${year}`;\n}\n\nfunction formatPrice(\n money: ShopifyProduct['priceRange']['minVariantPrice'],\n): string {\n const amount = parseFloat(money.amount);\n const formatted = amount.toFixed(2);\n return `${formatted} ${money.currencyCode}`;\n}\n\nfunction extractSizes(product: ShopifyProduct): string[] | undefined {\n const sizes = product.variants.edges.flatMap((e) =>\n e.node.selectedOptions\n .filter((o) => o.name.toLowerCase() === 'size')\n .map((o) => o.value),\n );\n const unique = [...new Set(sizes)];\n return unique.length > 0 ? unique : undefined;\n}\n\n/**\n * Extract variant ID → size label mapping for add-to-cart.\n * Shopify global IDs look like \"gid://shopify/ProductVariant/12345\" —\n * we extract just the numeric part for the Ajax API.\n */\nfunction extractVariants(product: ShopifyProduct): EntityItemData['variants'] {\n const variants = product.variants.edges.map((e) => {\n const sizeOption = e.node.selectedOptions.find(\n (o) => o.name.toLowerCase() === 'size',\n );\n const numericId = e.node.id.replace(\n /^gid:\\/\\/shopify\\/ProductVariant\\//,\n '',\n );\n return {\n label: sizeOption?.value || e.node.title,\n variantId: numericId,\n available: e.node.availableForSale,\n selectedOptions: e.node.selectedOptions.map((o) => ({\n name: o.name,\n value: o.value,\n })),\n price: formatPrice(e.node.price),\n compareAtPrice: e.node.compareAtPrice\n ? formatPrice(e.node.compareAtPrice)\n : undefined,\n };\n });\n return variants.length > 0 ? variants : undefined;\n}\n\nfunction getSecondaryImage(product: ShopifyProduct): string | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 1 ? images[1] : undefined;\n}\n\nfunction getAllImages(product: ShopifyProduct): string[] | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 0 ? images : undefined;\n}\n\nfunction getCompareAtPrice(product: ShopifyProduct): string | undefined {\n const compare = product.compareAtPriceRange?.minVariantPrice;\n if (!compare) {\n return undefined;\n }\n const min = product.priceRange.minVariantPrice;\n if (parseFloat(compare.amount) <= parseFloat(min.amount)) {\n return undefined;\n }\n return formatPrice(compare);\n}\n\n/**\n * The live entry for the variant the turn resolved, if the store still has it.\n * Both sides carry the bare numeric id — `extractVariants` strips the\n * `gid://shopify/ProductVariant/` prefix, and the turn never adds one.\n */\nfunction findLiveVariant(\n variants: EntityItemData['variants'],\n variantId: string | undefined,\n): NonNullable<EntityItemData['variants']>[number] | undefined {\n if (!variantId) {\n return undefined;\n }\n return variants?.find((variant) => variant.variantId === variantId);\n}\n\nexport function transformShopifyProduct(\n product: ShopifyProduct,\n variantId?: string,\n): EntityItemData {\n const variants = extractVariants(product);\n // The turn decided WHICH variant to show; the live fetch is here for what is\n // true about it right now.\n const matchedVariant = findLiveVariant(variants, variantId);\n // Whether this fetch is answering about the whole product or about one\n // child of it. A row that named a child but whose child the store no longer\n // returns — dropped since the turn, or past the `variants(first: 100)` page\n // — is NOT a whole-product row: answering it with the product's floor would\n // put a different variant's price under this variant's name, which is the\n // bug the row's own `variantId` exists to prevent. So the live price is\n // simply withheld and the turn's, which was right about this child, stands.\n const pricesWholeProduct = !variantId;\n\n return {\n url: product.onlineStoreUrl || '',\n title: product.title,\n type: 'product',\n description: product.description,\n imageUrl: product.featuredImage?.url,\n secondaryImageUrl: getSecondaryImage(product),\n vendor: product.vendor,\n price:\n matchedVariant?.price ??\n (pricesWholeProduct\n ? formatPrice(product.priceRange.minVariantPrice)\n : undefined),\n // The span the whole product covers, in the store's live currency. A card\n // reads it to decide whether \"from\" is honest; it is the product's either\n // way, so it is reported whether or not a child matched.\n priceMin: formatPrice(product.priceRange.minVariantPrice),\n priceMax: formatPrice(product.priceRange.maxVariantPrice),\n // Keyed on the variant, not on its value: a whole-product row falls back\n // to the product-level compare-at, but a matched variant that is simply\n // not on sale must not inherit one and print a false strikethrough.\n compareAtPrice: matchedVariant\n ? matchedVariant.compareAtPrice\n : pricesWholeProduct\n ? getCompareAtPrice(product)\n : undefined,\n available: matchedVariant?.available,\n sizes: extractSizes(product),\n variants,\n allImages: getAllImages(product),\n options: product.options?.length ? product.options : undefined,\n tags: product.tags.length ? product.tags : undefined,\n };\n}\n\nexport function transformShopifyCollection(\n collection: ShopifyCollection,\n): EntityItemData {\n return {\n url: collection.onlineStoreUrl || '',\n title: collection.title,\n type: 'collection',\n description: collection.description,\n imageUrl: collection.image?.url,\n };\n}\n\nexport function transformShopifyArticle(\n article: ShopifyArticle,\n): EntityItemData {\n return {\n url: article.onlineStoreUrl || '',\n title: article.title,\n type: 'article',\n description: article.excerpt || undefined,\n imageUrl: article.image?.url,\n publishDate: formatShopifyDate(article.publishedAt),\n author: article.authorV2?.name || undefined,\n tags: article.tags.length ? article.tags : undefined,\n };\n}\n\n/**\n * Transform any resolved Shopify entity to the standard EntityItemData shape.\n * Detects the entity type by checking for type-specific fields.\n */\nexport function transformShopifyEntityToItemData(\n entity: ShopifyProduct | ShopifyCollection | ShopifyArticle,\n entityType: string,\n variantId?: string,\n): EntityItemData {\n switch (entityType.toLowerCase()) {\n case 'product':\n return transformShopifyProduct(entity as ShopifyProduct, variantId);\n case 'collection':\n return transformShopifyCollection(entity as ShopifyCollection);\n case 'article':\n return transformShopifyArticle(entity as ShopifyArticle);\n default:\n return {\n url: '',\n title: (entity as { title?: string }).title || '',\n type: entityType,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,60 +1,67 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- import { headingMarkdown, bodyText, calloutFromParts } from './parse-utils.js';
2
+ import { headingMarkdown, bodyMarkdown } from './parse-utils.js';
3
3
  import { DIAGNOSTIC_TYPES } from '../diagnosticTypes.js';
4
4
  export class TextBlockSectionDefinition {
5
5
  constructor() {
6
6
  _defineProperty(this, "sectionType", 'textBlock');
7
- // The trailing callout is OPTIONAL. A heading plus running copy is the shape
8
- // stores emit constantly for explainer content, and requiring the blockquote
9
- // meant those blocks matched no definition at all and fell through to
10
- // `FallbackSectionDefinition` rendered as undifferentiated prose with no
11
- // section treatment. `parse()` already tolerates a missing callout (it only
12
- // rejects when title AND description are empty).
7
+ // A repeated body, not a single `t` a heading owns ALL the running copy
8
+ // beneath it. With one `t` the pattern took the heading and the FIRST
9
+ // paragraph only;
10
+ // because a pattern matches a PREFIX of the block, every later paragraph fell
11
+ // through to `FallbackSectionDefinition` and rendered as undifferentiated
12
+ // prose with no section treatment. Multi-paragraph explainer copy is the
13
+ // shape stores emit constantly, so most of the block was landing unstyled.
13
14
  //
14
- // NOTE for registry authors: with the callout optional this also matches a
15
- // bare `h2 > t`, and a pattern matches a PREFIX of the block. Register
15
+ // No callout in the grammar. A trailing blockquote is
16
+ // `CalloutSectionDefinition`'s (`html_comment? > callout+`) it sits above
17
+ // `textBlock` in `createSdkRegistry` and in every client registry, so once
18
+ // this pattern stops before the quote the parser's next iteration claims it
19
+ // as its own section. Keeping it here only duplicated that ownership.
20
+ //
21
+ // `link` is in the alternation because `convertToBlockElements` unwraps a
22
+ // paragraph into its inline nodes: `Try narrowing to [men](web5://ask/…)
23
+ // instead` emits `t link t`. With `t+` alone the run ended at that `link`,
24
+ // truncating the section at the first paragraph carrying an ask link — the
25
+ // exact shape DL #134 made the prompts emit. `parse()` uses `bodyMarkdown`,
26
+ // which keeps the link inline rather than deleting it the way `bodyText`
27
+ // does. Entity blocks are unaffected: their links are `link[entityType]` and
28
+ // every registry puts the entity definitions above this one.
29
+ //
30
+ // NOTE for registry authors: this still matches a bare `h2 > t`, so the
31
+ // prefix hazard is unchanged — if anything it claims MORE. Register
16
32
  // `textBlock` BELOW every definition whose block opens `h2 > t` — entity,
17
33
  // entityCollection, listItemsSection, featureCards, ctaBanner, comparison,
18
- // kpi, nextSteps. Above them it claims the heading + intro paragraph off the
34
+ // kpi, nextSteps. Above them it claims the heading + intro copy off the
19
35
  // front of the block and strands the list / links / table that follow
20
36
  // (`EntitySectionDefinition` allows `h2 > (t | callout)* > links…`, and
21
37
  // `listItemsSection` / `ctaBanner` are the same shape). `createSdkRegistry`
22
38
  // and the client registries in this repo order it that way; a client
23
39
  // registry living outside this repo has to do the same.
24
- _defineProperty(this, "patterns", ['html_comment? > h2 > t > callout?']);
40
+ _defineProperty(this, "patterns", ['html_comment? > h2 > (t | link)+']);
25
41
  _defineProperty(this, "defaults", {
26
42
  title: 'Text Block Title',
27
- description: 'Description text.',
28
- callout: {
29
- text: 'A key insight or quote.'
30
- }
43
+ description: 'Description text.'
31
44
  });
32
45
  _defineProperty(this, "fixtures", [{
33
46
  markdown: `## Choosing the Right Liquid Data Option
34
47
 
35
48
  Liquid Data Go is designed for brands that want affordable, scalable access to syndicated POS, shopper, and pricing insights with minimal setup and no data expertise required.
36
49
 
37
- > If you mainly need fast, packaged insights in one category, Liquid Data Go is usually the right fit; if you need to integrate many datasets and support multiple enterprise workflows, the broader Liquid Data platform and its solutions become more relevant.`,
50
+ It covers a single category out of the box, so a brand can stand up reporting without a data team and without negotiating a full platform contract first.
51
+
52
+ The broader Liquid Data platform becomes the better fit once you need to integrate many datasets and support multiple enterprise workflows.`,
38
53
  expected: {
39
54
  title: 'Choosing the Right Liquid Data Option',
40
- description: 'Liquid Data Go is designed for brands that want affordable, scalable access to syndicated POS, shopper, and pricing insights with minimal setup and no data expertise required.',
41
- callout: {
42
- text: 'If you mainly need fast, packaged insights in one category, Liquid Data Go is usually the right fit; if you need to integrate many datasets and support multiple enterprise workflows, the broader Liquid Data platform and its solutions become more relevant.'
43
- }
55
+ description: ['Liquid Data Go is designed for brands that want affordable, scalable access to syndicated POS, shopper, and pricing insights with minimal setup and no data expertise required.', 'It covers a single category out of the box, so a brand can stand up reporting without a data team and without negotiating a full platform contract first.', 'The broader Liquid Data platform becomes the better fit once you need to integrate many datasets and support multiple enterprise workflows.'].join('\n\n')
44
56
  }
45
57
  }, {
46
58
  markdown: `<!-- section: textBlock -->
47
59
  ## Key Takeaway
48
60
 
49
- The platform scales with your needs.
50
-
51
- > Start small with Go, then expand to the full suite as your data maturity grows.`,
61
+ The platform scales with your needs.`,
52
62
  expected: {
53
63
  title: 'Key Takeaway',
54
- description: 'The platform scales with your needs.',
55
- callout: {
56
- text: 'Start small with Go, then expand to the full suite as your data maturity grows.'
57
- }
64
+ description: 'The platform scales with your needs.'
58
65
  }
59
66
  }, {
60
67
  markdown: `## How to choose between a clinical serum and a nourishing dry oil
@@ -62,23 +69,20 @@ The platform scales with your needs.
62
69
  Clinically tested serums with patented actives target visible reduction of marks and uneven tone, while dry oils focus on broad nourishment and a satin finish.`,
63
70
  expected: {
64
71
  title: 'How to choose between a clinical serum and a nourishing dry oil',
65
- description: 'Clinically tested serums with patented actives target visible reduction of marks and uneven tone, while dry oils focus on broad nourishment and a satin finish.',
66
- callout: undefined
72
+ description: 'Clinically tested serums with patented actives target visible reduction of marks and uneven tone, while dry oils focus on broad nourishment and a satin finish.'
67
73
  }
68
74
  }]);
69
75
  }
70
76
  parse(parts, context) {
71
77
  const title = headingMarkdown(parts, 2) || '';
72
- const description = bodyText(parts) || '';
73
- const callout = calloutFromParts(parts);
78
+ const description = bodyMarkdown(parts) || '';
74
79
  if (!title && !description) {
75
80
  context == null || context.reportDiagnostic == null || context.reportDiagnostic(DIAGNOSTIC_TYPES.SECTION_REJECTED, 'textBlock: both title and description are empty');
76
81
  return null;
77
82
  }
78
83
  return {
79
84
  title,
80
- description,
81
- callout
85
+ description
82
86
  };
83
87
  }
84
88
  }
@@ -1 +1 @@
1
- {"version":3,"names":["headingMarkdown","bodyText","calloutFromParts","DIAGNOSTIC_TYPES","TextBlockSectionDefinition","constructor","_defineProperty","title","description","callout","text","markdown","expected","undefined","parse","parts","context","reportDiagnostic","SECTION_REJECTED"],"sources":["../../../../src/component/componentDefinitions/TextBlockSectionDefinition.ts"],"sourcesContent":["import type { Part } from '../../parts/parts';\nimport { headingMarkdown, bodyText, calloutFromParts } from './parse-utils';\nimport type {\n TextBlockCompProps,\n TextBlockSectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\nexport class TextBlockSectionDefinition\n implements SectionDefinition<TextBlockSectionComponent>\n{\n sectionType = 'textBlock' as const;\n // The trailing callout is OPTIONAL. A heading plus running copy is the shape\n // stores emit constantly for explainer content, and requiring the blockquote\n // meant those blocks matched no definition at all and fell through to\n // `FallbackSectionDefinition` rendered as undifferentiated prose with no\n // section treatment. `parse()` already tolerates a missing callout (it only\n // rejects when title AND description are empty).\n //\n // NOTE for registry authors: with the callout optional this also matches a\n // bare `h2 > t`, and a pattern matches a PREFIX of the block. Register\n // `textBlock` BELOW every definition whose block opens `h2 > t` — entity,\n // entityCollection, listItemsSection, featureCards, ctaBanner, comparison,\n // kpi, nextSteps. Above them it claims the heading + intro paragraph off the\n // front of the block and strands the list / links / table that follow\n // (`EntitySectionDefinition` allows `h2 > (t | callout)* > links…`, and\n // `listItemsSection` / `ctaBanner` are the same shape). `createSdkRegistry`\n // and the client registries in this repo order it that way; a client\n // registry living outside this repo has to do the same.\n patterns = ['html_comment? > h2 > t > callout?'];\n\n defaults: TextBlockCompProps = {\n title: 'Text Block Title',\n description: 'Description text.',\n callout: { text: 'A key insight or quote.' },\n };\n\n fixtures: SectionFixture<TextBlockCompProps>[] = [\n {\n markdown: `## Choosing the Right Liquid Data Option\n\nLiquid Data Go is designed for brands that want affordable, scalable access to syndicated POS, shopper, and pricing insights with minimal setup and no data expertise required.\n\n> If you mainly need fast, packaged insights in one category, Liquid Data Go is usually the right fit; if you need to integrate many datasets and support multiple enterprise workflows, the broader Liquid Data platform and its solutions become more relevant.`,\n expected: {\n title: 'Choosing the Right Liquid Data Option',\n description:\n 'Liquid Data Go is designed for brands that want affordable, scalable access to syndicated POS, shopper, and pricing insights with minimal setup and no data expertise required.',\n callout: {\n text: 'If you mainly need fast, packaged insights in one category, Liquid Data Go is usually the right fit; if you need to integrate many datasets and support multiple enterprise workflows, the broader Liquid Data platform and its solutions become more relevant.',\n },\n },\n },\n {\n markdown: `<!-- section: textBlock -->\n## Key Takeaway\n\nThe platform scales with your needs.\n\n> Start small with Go, then expand to the full suite as your data maturity grows.`,\n expected: {\n title: 'Key Takeaway',\n description: 'The platform scales with your needs.',\n callout: {\n text: 'Start small with Go, then expand to the full suite as your data maturity grows.',\n },\n },\n },\n {\n markdown: `## How to choose between a clinical serum and a nourishing dry oil\n\nClinically tested serums with patented actives target visible reduction of marks and uneven tone, while dry oils focus on broad nourishment and a satin finish.`,\n expected: {\n title: 'How to choose between a clinical serum and a nourishing dry oil',\n description:\n 'Clinically tested serums with patented actives target visible reduction of marks and uneven tone, while dry oils focus on broad nourishment and a satin finish.',\n callout: undefined,\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): TextBlockCompProps | null {\n const title = headingMarkdown(parts, 2) || '';\n const description = bodyText(parts) || '';\n const callout = calloutFromParts(parts);\n\n if (!title && !description) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'textBlock: both title and description are empty',\n );\n return null;\n }\n\n return { title, description, callout };\n }\n}\n"],"mappings":";AACA,SAASA,eAAe,EAAEC,QAAQ,EAAEC,gBAAgB,QAAQ,eAAe;AAO3E,SAASC,gBAAgB,QAAQ,oBAAoB;AAErD,OAAO,MAAMC,0BAA0B,CAEvC;EAAAC,YAAA;IAAAC,eAAA,sBACgB,WAAW;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAAAA,eAAA,mBACW,CAAC,mCAAmC,CAAC;IAAAA,eAAA,mBAEjB;MAC7BC,KAAK,EAAE,kBAAkB;MACzBC,WAAW,EAAE,mBAAmB;MAChCC,OAAO,EAAE;QAAEC,IAAI,EAAE;MAA0B;IAC7C,CAAC;IAAAJ,eAAA,mBAEgD,CAC/C;MACEK,QAAQ,EAAE;AAChB;AACA;AACA;AACA,kQAAkQ;MAC5PC,QAAQ,EAAE;QACRL,KAAK,EAAE,uCAAuC;QAC9CC,WAAW,EACT,iLAAiL;QACnLC,OAAO,EAAE;UACPC,IAAI,EAAE;QACR;MACF;IACF,CAAC,EACD;MACEC,QAAQ,EAAE;AAChB;AACA;AACA;AACA;AACA,kFAAkF;MAC5EC,QAAQ,EAAE;QACRL,KAAK,EAAE,cAAc;QACrBC,WAAW,EAAE,sCAAsC;QACnDC,OAAO,EAAE;UACPC,IAAI,EAAE;QACR;MACF;IACF,CAAC,EACD;MACEC,QAAQ,EAAE;AAChB;AACA,gKAAgK;MAC1JC,QAAQ,EAAE;QACRL,KAAK,EAAE,iEAAiE;QACxEC,WAAW,EACT,iKAAiK;QACnKC,OAAO,EAAEI;MACX;IACF,CAAC,CACF;EAAA;EAEDC,KAAKA,CAACC,KAAa,EAAEC,OAAsB,EAA6B;IACtE,MAAMT,KAAK,GAAGP,eAAe,CAACe,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE;IAC7C,MAAMP,WAAW,GAAGP,QAAQ,CAACc,KAAK,CAAC,IAAI,EAAE;IACzC,MAAMN,OAAO,GAAGP,gBAAgB,CAACa,KAAK,CAAC;IAEvC,IAAI,CAACR,KAAK,IAAI,CAACC,WAAW,EAAE;MAC1BQ,OAAO,YAAPA,OAAO,CAAEC,gBAAgB,YAAzBD,OAAO,CAAEC,gBAAgB,CACvBd,gBAAgB,CAACe,gBAAgB,EACjC,iDACF,CAAC;MACD,OAAO,IAAI;IACb;IAEA,OAAO;MAAEX,KAAK;MAAEC,WAAW;MAAEC;IAAQ,CAAC;EACxC;AACF","ignoreList":[]}
1
+ {"version":3,"names":["headingMarkdown","bodyMarkdown","DIAGNOSTIC_TYPES","TextBlockSectionDefinition","constructor","_defineProperty","title","description","markdown","expected","join","parse","parts","context","reportDiagnostic","SECTION_REJECTED"],"sources":["../../../../src/component/componentDefinitions/TextBlockSectionDefinition.ts"],"sourcesContent":["import type { Part } from '../../parts/parts';\nimport { headingMarkdown, bodyMarkdown } from './parse-utils';\nimport type {\n TextBlockCompProps,\n TextBlockSectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\nexport class TextBlockSectionDefinition\n implements SectionDefinition<TextBlockSectionComponent>\n{\n sectionType = 'textBlock' as const;\n // A repeated body, not a single `t` — a heading owns ALL the running copy\n // beneath it. With one `t` the pattern took the heading and the FIRST\n // paragraph only;\n // because a pattern matches a PREFIX of the block, every later paragraph fell\n // through to `FallbackSectionDefinition` and rendered as undifferentiated\n // prose with no section treatment. Multi-paragraph explainer copy is the\n // shape stores emit constantly, so most of the block was landing unstyled.\n //\n // No callout in the grammar. A trailing blockquote is\n // `CalloutSectionDefinition`'s (`html_comment? > callout+`) it sits above\n // `textBlock` in `createSdkRegistry` and in every client registry, so once\n // this pattern stops before the quote the parser's next iteration claims it\n // as its own section. Keeping it here only duplicated that ownership.\n //\n // `link` is in the alternation because `convertToBlockElements` unwraps a\n // paragraph into its inline nodes: `Try narrowing to [men](web5://ask/…)\n // instead` emits `t link t`. With `t+` alone the run ended at that `link`,\n // truncating the section at the first paragraph carrying an ask link — the\n // exact shape DL #134 made the prompts emit. `parse()` uses `bodyMarkdown`,\n // which keeps the link inline rather than deleting it the way `bodyText`\n // does. Entity blocks are unaffected: their links are `link[entityType]` and\n // every registry puts the entity definitions above this one.\n //\n // NOTE for registry authors: this still matches a bare `h2 > t`, so the\n // prefix hazard is unchanged if anything it claims MORE. Register\n // `textBlock` BELOW every definition whose block opens `h2 > t` — entity,\n // entityCollection, listItemsSection, featureCards, ctaBanner, comparison,\n // kpi, nextSteps. Above them it claims the heading + intro copy off the\n // front of the block and strands the list / links / table that follow\n // (`EntitySectionDefinition` allows `h2 > (t | callout)* > links…`, and\n // `listItemsSection` / `ctaBanner` are the same shape). `createSdkRegistry`\n // and the client registries in this repo order it that way; a client\n // registry living outside this repo has to do the same.\n patterns = ['html_comment? > h2 > (t | link)+'];\n\n defaults: TextBlockCompProps = {\n title: 'Text Block Title',\n description: 'Description text.',\n };\n\n fixtures: SectionFixture<TextBlockCompProps>[] = [\n {\n markdown: `## Choosing the Right Liquid Data Option\n\nLiquid Data Go is designed for brands that want affordable, scalable access to syndicated POS, shopper, and pricing insights with minimal setup and no data expertise required.\n\nIt covers a single category out of the box, so a brand can stand up reporting without a data team and without negotiating a full platform contract first.\n\nThe broader Liquid Data platform becomes the better fit once you need to integrate many datasets and support multiple enterprise workflows.`,\n expected: {\n title: 'Choosing the Right Liquid Data Option',\n description: [\n 'Liquid Data Go is designed for brands that want affordable, scalable access to syndicated POS, shopper, and pricing insights with minimal setup and no data expertise required.',\n 'It covers a single category out of the box, so a brand can stand up reporting without a data team and without negotiating a full platform contract first.',\n 'The broader Liquid Data platform becomes the better fit once you need to integrate many datasets and support multiple enterprise workflows.',\n ].join('\\n\\n'),\n },\n },\n {\n markdown: `<!-- section: textBlock -->\n## Key Takeaway\n\nThe platform scales with your needs.`,\n expected: {\n title: 'Key Takeaway',\n description: 'The platform scales with your needs.',\n },\n },\n {\n markdown: `## How to choose between a clinical serum and a nourishing dry oil\n\nClinically tested serums with patented actives target visible reduction of marks and uneven tone, while dry oils focus on broad nourishment and a satin finish.`,\n expected: {\n title:\n 'How to choose between a clinical serum and a nourishing dry oil',\n description:\n 'Clinically tested serums with patented actives target visible reduction of marks and uneven tone, while dry oils focus on broad nourishment and a satin finish.',\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): TextBlockCompProps | null {\n const title = headingMarkdown(parts, 2) || '';\n const description = bodyMarkdown(parts) || '';\n\n if (!title && !description) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'textBlock: both title and description are empty',\n );\n return null;\n }\n\n return { title, description };\n }\n}\n"],"mappings":";AACA,SAASA,eAAe,EAAEC,YAAY,QAAQ,eAAe;AAO7D,SAASC,gBAAgB,QAAQ,oBAAoB;AAErD,OAAO,MAAMC,0BAA0B,CAEvC;EAAAC,YAAA;IAAAC,eAAA,sBACgB,WAAW;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAAAA,eAAA,mBACW,CAAC,kCAAkC,CAAC;IAAAA,eAAA,mBAEhB;MAC7BC,KAAK,EAAE,kBAAkB;MACzBC,WAAW,EAAE;IACf,CAAC;IAAAF,eAAA,mBAEgD,CAC/C;MACEG,QAAQ,EAAE;AAChB;AACA;AACA;AACA;AACA;AACA,4IAA4I;MACtIC,QAAQ,EAAE;QACRH,KAAK,EAAE,uCAAuC;QAC9CC,WAAW,EAAE,CACX,iLAAiL,EACjL,2JAA2J,EAC3J,6IAA6I,CAC9I,CAACG,IAAI,CAAC,MAAM;MACf;IACF,CAAC,EACD;MACEF,QAAQ,EAAE;AAChB;AACA;AACA,qCAAqC;MAC/BC,QAAQ,EAAE;QACRH,KAAK,EAAE,cAAc;QACrBC,WAAW,EAAE;MACf;IACF,CAAC,EACD;MACEC,QAAQ,EAAE;AAChB;AACA,gKAAgK;MAC1JC,QAAQ,EAAE;QACRH,KAAK,EACH,iEAAiE;QACnEC,WAAW,EACT;MACJ;IACF,CAAC,CACF;EAAA;EAEDI,KAAKA,CAACC,KAAa,EAAEC,OAAsB,EAA6B;IACtE,MAAMP,KAAK,GAAGN,eAAe,CAACY,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE;IAC7C,MAAML,WAAW,GAAGN,YAAY,CAACW,KAAK,CAAC,IAAI,EAAE;IAE7C,IAAI,CAACN,KAAK,IAAI,CAACC,WAAW,EAAE;MAC1BM,OAAO,YAAPA,OAAO,CAAEC,gBAAgB,YAAzBD,OAAO,CAAEC,gBAAgB,CACvBZ,gBAAgB,CAACa,gBAAgB,EACjC,iDACF,CAAC;MACD,OAAO,IAAI;IACb;IAEA,OAAO;MAAET,KAAK;MAAEC;IAAY,CAAC;EAC/B;AACF","ignoreList":[]}