@syntrologie/adapt-product 2.31.1 → 2.32.0

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.
package/dist/cdn.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  onActivate,
3
3
  runtime
4
- } from "./chunk-47AQYHIE.js";
5
- import "./chunk-7Z4Y44WC.js";
4
+ } from "./chunk-QN24QEGC.js";
5
+ import "./chunk-GL5TWSUQ.js";
6
6
 
7
7
  // src/cdn.ts
8
8
  var manifest = {
@@ -204,8 +204,13 @@ var cardSchema = z2.object({
204
204
  });
205
205
  var comparisonRowSchema = z2.object({
206
206
  label: z2.string(),
207
- valueA: z2.string(),
208
- valueB: z2.string(),
207
+ // Schema floor (mainpeptides filler-cell incident): a row's first two cells
208
+ // are always visitor-facing (a comparison mounts ≥2 products) — an empty
209
+ // cell is a "we don't know" in a tile that exists to answer exactly that
210
+ // question. Whitespace-only cells are caught at render (ProductComparisonLit
211
+ // drops the row); the server-side filler gate rejects placeholder prose.
212
+ valueA: z2.string().min(1),
213
+ valueB: z2.string().min(1),
209
214
  valueC: z2.string().optional(),
210
215
  valueD: z2.string().optional()
211
216
  });
@@ -316,4 +321,4 @@ export {
316
321
  tileWidgets,
317
322
  CAPABILITIES_DOCUMENTATION
318
323
  };
319
- //# sourceMappingURL=chunk-7Z4Y44WC.js.map
324
+ //# sourceMappingURL=chunk-GL5TWSUQ.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/schema.ts", "../src/schema-primitives.ts", "../src/platformAdapter.ts"],
4
+ "sourcesContent": ["import { z } from 'zod';\nimport { productSchema } from './schema-primitives';\n\n// Top-level schemas use Zod's default `.strip()` mode so the runtime-injected\n// fields ``chromeless`` and ``instanceId`` that SyntroTileCard adds to every\n// widget mount don't fail validation. The inner ``productSchema`` remains\n// strict \u2014 that's the actual product contract, and unknown fields there\n// would signal author error rather than runtime envelope leakage.\n// Keys the LLM may include in `visibleFacts` to curate which baked facts\n// render on a card. `name` / `image` / `ctas` are identity + PDP CTA and\n// always show \u2014 they're not toggleable. Omitting `visibleFacts` renders\n// every populated block (back-compat for configs that never opted in).\nexport const VISIBLE_FACT_KEYS = [\n 'tagline',\n 'price',\n 'rating',\n 'availability',\n 'badge',\n 'specs',\n 'framing',\n] as const;\n\nexport type VisibleFactKey = (typeof VISIBLE_FACT_KEYS)[number];\n\nexport const cardSchema = z.object({\n product: productSchema,\n density: z.enum(['compact', 'standard']).default('standard'),\n /**\n * LLM-curated subset of facts to render, in author order. When omitted,\n * the card renders every populated block. When set, only listed keys\n * render (plus the always-shown identity + CTAs). List order = render\n * order \u2014 `[\"rating\", \"price\"]` and `[\"price\", \"rating\"]` produce\n * different layouts.\n */\n visibleFacts: z.array(z.enum(VISIBLE_FACT_KEYS)).max(VISIBLE_FACT_KEYS.length).optional(),\n // NOTE: variant presence is NOT a config/LLM field. Whether a product has\n // selectable SKUs is a CATALOG fact, resolved at render from the platform\n // adapter's fetchVariants (host API for real tenants, seed data for the\n // demo). The card derives its Add-to-cart behavior from that fetched payload\n // \u2014 see ProductCardLit `#hasSelectableVariants`. A stored `has_variants`\n // flag drifts from the catalog and can't be known by the LLM, so it was\n // removed (BUG-1783100102 follow-up).\n});\n\n/**\n * One comparison dimension, rendered as a matrix row. `valueA`..`valueD` are the\n * cells for `products[0]`..`products[3]` (a row need only fill as many as there\n * are products; missing cells render as \"\u2014\"). Flat scalar columns \u2014 NOT a nested\n * array \u2014 so the whole row is authorable as one `struct_list` item by the\n * assistant. This is the LLM-authorable shape of a comparison: a flat list of\n * rows the assistant fills to answer the visitor's actual question, aligned by\n * construction (each row carries every product's value), unlike per-product\n * attribute lists which drift out of sync.\n */\nexport const comparisonRowSchema = z.object({\n label: z.string(),\n // Schema floor (mainpeptides filler-cell incident): a row's first two cells\n // are always visitor-facing (a comparison mounts \u22652 products) \u2014 an empty\n // cell is a \"we don't know\" in a tile that exists to answer exactly that\n // question. Whitespace-only cells are caught at render (ProductComparisonLit\n // drops the row); the server-side filler gate rejects placeholder prose.\n valueA: z.string().min(1),\n valueB: z.string().min(1),\n valueC: z.string().optional(),\n valueD: z.string().optional(),\n});\n\nexport const comparisonSchema = z.object({\n products: z.array(productSchema).min(2).max(4),\n // The comparison ALWAYS renders as a side-by-side matrix (products = columns,\n // rows = dimensions) \u2014 that's the whole point of a comparison. When the\n // assistant authors `rows`, those are used verbatim; when omitted, the widget\n // synthesizes rows from each product's `attributes`. There is deliberately NO\n // layout toggle: the LLM can't choose a stacked/accordion presentation.\n rows: z.array(comparisonRowSchema).optional(),\n heading: z.string().optional(),\n});\n\nexport const heroSchema = z.object({\n product: productSchema,\n layout: z.enum(['image-left', 'image-right', 'image-top']).default('image-left'),\n longCopy: z.string().max(800).optional(),\n});\n\nexport const gridSchema = z.object({\n products: z.array(productSchema).min(2).max(12),\n desktopColumns: z.union([z.literal(2), z.literal(3)]).default(3),\n heading: z.string().optional(),\n});\n\n// [Adversarial SEC] scheme-validate pdp_href \u2014 block javascript:/data:/vbscript:\n// by requiring the URL to start with a known-safe scheme or relative path.\nconst SAFE_HREF = /^(https?:\\/\\/|\\/|\\.\\/|\\.\\.\\/|#|\\?|mailto:|tel:)/i;\n\n// nullish (not optional): the runtime-backend serializes Pydantic None as\n// JSON null \u2014 optional() alone rejects null, which silently blanked every\n// mounted carousel with an unset image/price/hook (BUG-1783408976).\nexport const recoItemSchema = z.object({\n image_url: z.string().nullish(),\n price: z.string().nullish(),\n hook: z.string().max(40).nullish(),\n pdp_href: z.string().regex(SAFE_HREF, 'pdp_href must be a safe path/URL'),\n});\n\nexport const recoSchema = z.object({\n heading: z.string().nullish(),\n items: z.array(recoItemSchema).max(12).default([]),\n});\n\nexport type CardProps = z.infer<typeof cardSchema>;\nexport type ComparisonProps = z.infer<typeof comparisonSchema>;\nexport type HeroProps = z.infer<typeof heroSchema>;\nexport type GridProps = z.infer<typeof gridSchema>;\nexport type RecoProps = z.infer<typeof recoSchema>;\nexport type RecoItemProps = z.infer<typeof recoItemSchema>;\n\nexport const tileWidgets = [\n { widget: 'adaptive-product:card', defName: 'productCardProps', propsSchema: cardSchema },\n {\n widget: 'adaptive-product:comparison',\n defName: 'productComparisonProps',\n propsSchema: comparisonSchema,\n },\n { widget: 'adaptive-product:hero', defName: 'productHeroProps', propsSchema: heroSchema },\n { widget: 'adaptive-product:grid', defName: 'productGridProps', propsSchema: gridSchema },\n { widget: 'adaptive-product:reco', defName: 'productRecoProps', propsSchema: recoSchema },\n] as const;\n\n// ============================================================================\n// Capabilities Documentation (injected into JSON Schema for LLM prompts)\n// ============================================================================\n\nexport const CAPABILITIES_DOCUMENTATION = {\n packageId: 'adaptive-product',\n plannerSummary:\n 'Product merchandising widgets for cards, comparisons, heroes, grids, and recommendation carousels. Reach for this when the strategy needs product facts, PDP support, recommendations, or commerce-oriented social proof.',\n description:\n 'Product adaptive for presenting catalog-backed product facts and recommendations. Widgets support product cards, side-by-side comparisons, hero blocks, product grids, recommendation carousels, and a host-owned PDP element.',\n whenToUse: [\n {\n goal: 'Show one curated product with controlled visible facts',\n action:\n 'Add a tile with widget: \"adaptive-product:card\" and provide product, density, and optional visibleFacts props.',\n },\n {\n goal: 'Compare two to four products side by side',\n action:\n 'Add a tile with widget: \"adaptive-product:comparison\"; products become columns and optional rows define the comparison dimensions.',\n },\n {\n goal: 'Feature one product in a larger editorial layout',\n action:\n 'Add a tile with widget: \"adaptive-product:hero\" and choose image-left, image-right, or image-top layout.',\n },\n {\n goal: 'Show a small collection of products',\n action:\n 'Add a tile with widget: \"adaptive-product:grid\" for 2-12 products and choose two or three desktop columns.',\n },\n {\n goal: 'Show recommended next products or actions',\n action:\n 'Add a tile with widget: \"adaptive-product:reco\" and provide up to 12 recommendation items with safe pdp_href values.',\n },\n ],\n conventions: [\n {\n name: 'Catalog facts stay inside product',\n description:\n 'Use the product object for identity, image, price, rating, availability, badge, specs, framing, and CTAs. Keep visibleFacts top-level on card props; it is not nested under product.',\n },\n {\n name: 'Comparison has one presentation',\n description:\n 'adaptive-product:comparison always renders a side-by-side matrix. Do not add layout toggles such as stacked, accordion, expandBehavior, or defaultExpandedIndex.',\n },\n {\n name: 'Recommendation links must be safe',\n description:\n 'reco item pdp_href values must use http(s), relative paths, hashes, query strings, mailto, or tel. javascript:, data:, and other executable schemes are rejected.',\n },\n {\n name: 'Live binding is a product adapter concern',\n description:\n 'Price, image, variant, Shopify, WooCommerce, JSON-LD, and OpenGraph binding are resolved through the adaptive-product platform adapter, not through runtime-specific branches.',\n },\n ],\n events: [],\n};\n\nexport type {\n AddToCartInput,\n AddToCartResult,\n AttributeDef,\n AttributeValue,\n ProductPlatformAdapter,\n SwatchSpec,\n Variant,\n VariantPayload,\n} from './platformAdapter';\nexport { getProductPlatformAdapter } from './platformAdapter';\nexport type { ImageBind, PriceBind, Product } from './schema-primitives';\nexport { productSchema } from './schema-primitives';\n\n// \u2500\u2500 Syntro PDP (config-first personalized PDP; live slots via /api/pdp/module) \u2500\n\nexport interface SyntroPdpProps {\n /** Stable product identifier passed to the backend planner. */\n productId: string;\n /** Display name used by the planner when authoring headers / copy. */\n productName: string;\n /** Optional short product blurb passed verbatim into the planner. */\n productBlurb?: string;\n}\n", "// src/schema-primitives.ts\nimport { z } from 'zod';\n\nconst SAFE_HREF_PATTERN =\n /^(?:[/.#?]|[hH][tT][tT][pP][sS]?:\\/\\/|[mM][aA][iI][lL][tT][oO]:|[tT][eE][lL]:)/;\n\nexport const HrefZ = z\n .string()\n .min(1)\n .regex(SAFE_HREF_PATTERN, {\n message:\n 'href must start with /, ./, ../, #, ?, http(s)://, mailto:, or tel: \u2014 javascript:, data:, and vbscript: are not allowed',\n })\n .refine(\n (s) => {\n try {\n const parsed = new URL(s, 'https://syntro.local/');\n return ['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n },\n { message: 'href resolves to an unsupported protocol' }\n );\n\nexport const PriceBindZ = z.discriminatedUnion('type', [\n z.object({ type: z.literal('dom-selector'), selector: z.string().min(1) }).strict(),\n z\n .object({ type: z.literal('shopify-ajax'), handle: z.string().min(1), base: HrefZ.optional() })\n .strict(),\n z\n .object({\n type: z.literal('woocommerce-store-api'),\n slug: z.string().min(1),\n base: HrefZ.optional(),\n })\n .strict(),\n z.object({ type: z.literal('jsonld'), url: HrefZ.optional() }).strict(),\n]);\n\nexport const ImageBindZ = z.discriminatedUnion('type', [\n z.object({ type: z.literal('dom-selector'), selector: z.string().min(1) }).strict(),\n z\n .object({ type: z.literal('shopify-ajax'), handle: z.string().min(1), base: HrefZ.optional() })\n .strict(),\n z.object({ type: z.literal('opengraph'), url: HrefZ }).strict(),\n z.object({ type: z.literal('jsonld'), url: HrefZ.optional() }).strict(),\n]);\n\n// ---------------------------------------------------------------------------\n// productSchema v2 \u2014 Google Merchant Center / schema.org/Product alignment\n//\n// Layered model:\n// 1. Strict typed core (id, name, image, price, ctas) \u2014 rendering depends on\n// these; unchanged from v1.\n// 2. Common typed optional (brand, availability, salePrice, rating) \u2014 widely\n// reused across product types; renderers know what to do with them.\n// 3. `specs[]` \u2014 open spec table modelled on Google's `product_detail[]` /\n// schema.org's `additionalProperty`. Arbitrary `{section, name, value}`\n// triples for category-specific facts. Cards group by section.\n// 4. `labels{}` \u2014 Google `custom_label_0..4` style: free-form targeting\n// data the renderer ignores and the targeting layer reads.\n//\n// `attributes[]` from v1 is preserved as an optional, deprecated field so\n// existing configs keep working. Renderers should treat it as a synthetic\n// \"About\" spec section. New configs should use `specs[]`.\n// ---------------------------------------------------------------------------\n\nconst specRowSchema = z\n .object({\n section: z.string().min(1).describe('Section header \u2014 \"Supplement Facts\", \"Shipping\", etc.'),\n name: z.string().min(1).describe('Attribute name \u2014 \"Vitamin D3\", \"Lead time\"'),\n value: z.string().describe('Attribute value as a string \u2014 \"5000\", \"2\"'),\n unit: z\n .string()\n .optional()\n .describe('Unit suffix shown small next to value \u2014 \"IU\", \"mg\", \"days\"'),\n emphasis: z.boolean().default(false),\n priority: z\n .number()\n .optional()\n .describe('Higher = more important. Compact cards show top N by priority.'),\n })\n .strict();\n\nexport const productSchema = z\n .object({\n // \u2500\u2500 Tier 1: strict typed core \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n id: z.string().min(1).describe('Stable identifier for analytics and dedup'),\n name: z.string().min(1),\n tagline: z.string().optional(),\n image: z\n .object({\n src: HrefZ,\n alt: z.string(),\n bind: ImageBindZ.optional(),\n })\n .strict(),\n price: z\n .object({\n amount: z.string(),\n cadence: z.string().optional(),\n bind: PriceBindZ.optional(),\n })\n .strict()\n .optional(),\n badge: z\n .object({\n label: z.string(),\n tone: z.enum(['neutral', 'primary', 'success', 'warning']).default('neutral'),\n })\n .strict()\n .optional(),\n framing: z.string().max(280).optional(),\n ctas: z\n .array(\n z\n .object({\n label: z.string().min(1),\n href: HrefZ.optional(),\n target: z.enum(['_self', '_blank']).default('_self'),\n variant: z.enum(['primary', 'secondary', 'ghost']).default('primary'),\n actionId: z.string().optional(),\n })\n .strict()\n )\n .min(1)\n .max(2),\n\n // \u2500\u2500 Tier 2: common typed optional (mirrors GMC / schema.org Offer) \u2500\n /** Cross-platform product identifiers. SKU/GTIN/MPN per GMC; platform handles for bind auto-resolution. */\n identifiers: z\n .object({\n sku: z.string().optional(),\n gtin: z.string().optional(),\n mpn: z.string().optional(),\n shopifyHandle: z.string().optional(),\n wooSlug: z.string().optional(),\n })\n .strict()\n .optional(),\n brand: z.string().optional(),\n /** GMC availability enum \u2014 drives default badge if no explicit badge set. */\n availability: z.enum(['in_stock', 'preorder', 'backorder', 'out_of_stock']).optional(),\n /** Strikethrough source price. `price` becomes the \"was\" value when this is set. */\n salePrice: z\n .object({\n amount: z.string(),\n cadence: z.string().optional(),\n validUntil: z.string().datetime().optional(),\n bind: PriceBindZ.optional(),\n })\n .strict()\n .optional(),\n /** AggregateRating \u2014 value, count, optional max (defaults to 5). */\n rating: z\n .object({\n value: z.number().min(0),\n count: z.number().int().min(0),\n max: z.number().default(5),\n })\n .strict()\n .optional(),\n\n // \u2500\u2500 Tier 3: open spec table (Google product_detail / schema.org additionalProperty) \u2500\u2500\n /** Category-specific spec rows grouped by section. Renderers show as grouped tables. */\n specs: z.array(specRowSchema).max(32).optional(),\n\n // \u2500\u2500 Tier 4: passthrough targeting labels (Google custom_label_*) \u2500\u2500\u2500\n /** Flat record of targeting/segmentation labels. Renderer ignores; targeting layer reads. */\n labels: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),\n\n // \u2500\u2500 Deprecated: v1 flat attributes (renderers shim into a synthetic \"About\" spec section) \u2500\u2500\n /** @deprecated Use `specs[]` with `section: \"About\"`. Retained for backward compatibility. */\n attributes: z\n .array(\n z\n .object({\n label: z.string(),\n value: z.string(),\n emphasis: z.boolean().default(false),\n })\n .strict()\n )\n .max(8)\n .optional(),\n\n /**\n * Optional variant block for products with attribute-based SKUs.\n * When present, indicates the product has multiple SKUs distinguished\n * by attributes (size, color, etc.). This block on the product itself\n * is the seed-data shape the demo uses to serve its Shopify\n * GET /products/{handle}.js endpoint.\n *\n * The card does NOT read this directly to decide whether to show a\n * variant picker: variant presence is resolved at render from the\n * platform adapter's fetchVariants (host API for real tenants, this\n * seed data for the demo), so a single catalog source drives it.\n * For real Shopify/Woo tenants this field is unused on the mount\n * payload; the adapter fetches from the host API instead.\n */\n variants: z\n .object({\n attributes: z.array(\n z.object({\n key: z.string(),\n label: z.string(),\n kind: z.enum(['pill', 'swatch']),\n values: z.array(\n z.object({\n id: z.string(),\n label: z.string(),\n swatch: z\n .object({\n kind: z.enum(['color', 'image']),\n value: z.string(),\n })\n .nullable(),\n })\n ),\n })\n ),\n variants: z.array(\n z.object({\n id: z.string(),\n selection: z.record(z.string(), z.string()),\n price: z.number(),\n compare_at_price: z.number().nullable(),\n in_stock: z.boolean(),\n // Same URL-safety policy as image.src \u2014 this block is config/LLM-\n // authored, so javascript:/data:/vbscript: must be rejected here too.\n featured_image: HrefZ.optional(),\n })\n ),\n })\n .optional(),\n })\n .strict();\n\nexport type Product = z.infer<typeof productSchema>;\nexport type PriceBind = z.infer<typeof PriceBindZ>;\nexport type ImageBind = z.infer<typeof ImageBindZ>;\n", "/**\n * Platform-agnostic commerce interface the product-card widget reads to fetch\n * variants, add to cart, and open the cart. The widget never branches on\n * platform \u2014 it just reads whatever adapter the RUNTIME published for the\n * detected platform.\n *\n * Implementations live in the platform packages and are wired in by the runtime\n * (see runtime-sdk `_platforms-registered.ts` \u2192 `setProductCommerceGlobal`):\n * - platform-shopify: /products/{handle}.js + /cart/add.js + navigate to /cart.\n * - platform-woocommerce (future): Store API + ?wc-ajax=add_to_cart.\n *\n * This is NOT host-injected. The host page supplies nothing.\n */\nexport interface ProductPlatformAdapter {\n /** Fetch normalized variant data for a product. */\n fetchVariants(productId: string): Promise<VariantPayload>;\n\n /** Perform the platform-specific add-to-cart POST. Returns success or\n * a structured error the widget can render. */\n addToCart(input: AddToCartInput): Promise<AddToCartResult>;\n\n /** Open the host's cart UI. Implementation is entirely platform-specific.\n * The widget calls this AFTER closing the chat canvas. */\n openCart(): Promise<void>;\n}\n\nexport interface AddToCartInput {\n product_id: string;\n /** null for single-SKU products (no variant to pick). */\n variant_id: string | null;\n quantity: number;\n}\n\nexport interface AddToCartResult {\n ok: boolean;\n /** Set when ok=false. */\n error?: { code: 'oos' | 'network' | 'validation' | 'unknown'; message: string };\n}\n\nexport interface VariantPayload {\n product_id: string;\n attributes: AttributeDef[];\n variants: Variant[];\n}\n\nexport interface AttributeDef {\n /** Stable id: 'size', 'color', 'cadence'. */\n key: string;\n /** Display label: 'Size', 'Color', 'Delivery'. */\n label: string;\n /** Render mode. Extensible: future kinds may include 'dropdown'. */\n kind: 'pill' | 'swatch';\n values: AttributeValue[];\n}\n\nexport interface AttributeValue {\n /** Stable id: 'large', 'red', 'subscribe'. */\n id: string;\n /** Display label: 'Large', 'Red', 'Subscribe \u2014 save 15%'. */\n label: string;\n /** Present when the attribute's kind is 'swatch'; null for pill attributes. */\n swatch: SwatchSpec | null;\n}\n\nexport interface SwatchSpec {\n kind: 'color' | 'image';\n /** Hex color (#000000) for kind 'color'; URL for kind 'image'. */\n value: string;\n}\n\nexport interface Variant {\n /** Platform-native SKU/variant id used as the cart-POST id. */\n id: string;\n /** Map of attribute key \u2192 value id, e.g. { size: 'large', color: 'red' }. */\n selection: Record<string, string>;\n price: number;\n compare_at_price: number | null;\n in_stock: boolean;\n /** Optional per-variant hero image URL. When present the variant panel swaps\n * its image to this on selection; otherwise it keeps the product-level image.\n * Populated by platforms that carry variant-level media (Shopify\n * `variant.featured_image`); absent for products whose image never varies by\n * SKU (e.g. pack-size only). */\n featured_image?: string;\n}\n\n// \u2500\u2500 Window-level lookup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The widget ships as a bundled IIFE with its own module closure, so a\n// module-level variable can't be shared with the runtime that sets the adapter.\n// The runtime publishes the detected platform's commerce adapter on this window\n// key (runtime-sdk `setProductCommerceGlobal`); the widget only reads it.\n// Returns null when no platform was detected \u2014 the widget then renders its\n// add-to-cart CTA disabled.\n\nconst ADAPTER_KEY = '__SYNTRO_PRODUCT_ADAPTER__';\n\n/** Retrieve the runtime-published commerce adapter. Null if none was set. */\nexport function getProductPlatformAdapter(): ProductPlatformAdapter | null {\n if (typeof window === 'undefined') return null;\n return (\n (window as unknown as Record<string, ProductPlatformAdapter | undefined>)[ADAPTER_KEY] ?? null\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["z", "z"]
7
+ }
@@ -10,7 +10,7 @@ import {
10
10
  gridSchema,
11
11
  heroSchema,
12
12
  recoSchema
13
- } from "./chunk-7Z4Y44WC.js";
13
+ } from "./chunk-GL5TWSUQ.js";
14
14
 
15
15
  // ../../../node_modules/@lit/context/lib/create-context.js
16
16
  function n(n2) {
@@ -5909,7 +5909,7 @@ function onImageError(e2) {
5909
5909
 
5910
5910
  // src/widgets/ProductComparisonLit.ts
5911
5911
  import { html as html8, LitElement as LitElement8, nothing as nothing4 } from "lit";
5912
- var _controller2, _parsed, _ProductComparisonLit_instances, healthProbe_fn2, _scrollListenerEl, _onBodyScroll, syncScrollCue_fn, startBind_fn2, renderDiveDeeper_fn, onDiveDeeper_fn, visibleRows_fn, maxFields_fn, synthesizeRows_fn, renderMatrix_fn;
5912
+ var _controller2, _parsed, _ProductComparisonLit_instances, healthProbe_fn2, _scrollListenerEl, _onBodyScroll, syncScrollCue_fn, startBind_fn2, renderDiveDeeper_fn, onDiveDeeper_fn, visibleRows_fn, rowComplete_fn, maxFields_fn, synthesizeRows_fn, renderMatrix_fn;
5913
5913
  var ProductComparisonLit = class extends LitElement8 {
5914
5914
  constructor() {
5915
5915
  super();
@@ -6141,10 +6141,16 @@ onDiveDeeper_fn = function(parsed) {
6141
6141
  * dimensions (authored/attribute order) survive the cut.
6142
6142
  */
6143
6143
  visibleRows_fn = function(parsed) {
6144
- const all = parsed.rows && parsed.rows.length > 0 ? parsed.rows : __privateMethod(this, _ProductComparisonLit_instances, synthesizeRows_fn).call(this, parsed.products);
6144
+ const all = parsed.rows && parsed.rows.length > 0 ? parsed.rows.filter((row) => __privateMethod(this, _ProductComparisonLit_instances, rowComplete_fn).call(this, row, parsed.products.length)) : __privateMethod(this, _ProductComparisonLit_instances, synthesizeRows_fn).call(this, parsed.products);
6145
6145
  const max = __privateMethod(this, _ProductComparisonLit_instances, maxFields_fn).call(this);
6146
6146
  return max == null ? all : all.slice(0, max);
6147
6147
  };
6148
+ /** True when the row has a non-empty (post-trim) cell for every mounted
6149
+ * product column — the first `productCount` of valueA..valueD. */
6150
+ rowComplete_fn = function(row, productCount) {
6151
+ const cells = [row.valueA, row.valueB, row.valueC, row.valueD].slice(0, productCount);
6152
+ return cells.every((cell) => (cell ?? "").trim().length > 0);
6153
+ };
6148
6154
  /** Canvas-set cap on comparison rows (`--sc-comparison-max-fields`), or null for
6149
6155
  * no cap. Read from computed style so it's the canvas's layout decision, not a
6150
6156
  * data/LLM field. */
@@ -11079,4 +11085,4 @@ export {
11079
11085
  * SPDX-License-Identifier: BSD-3-Clause
11080
11086
  *)
11081
11087
  */
11082
- //# sourceMappingURL=chunk-47AQYHIE.js.map
11088
+ //# sourceMappingURL=chunk-QN24QEGC.js.map