{
  "title": "Shopify variants - choose stores or product pages",
  "type": "object",
  "schemaVersion": 1,
  "properties": {
    "stores": {
      "title": "Store URLs",
      "description": "Public Shopify store homepages or locale roots, up to 10. When both source arrays are empty, uses a small Death Wish Coffee catalog sample.",
      "type": "array",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "maxItems": 10,
      "sectionCaption": "1 · Stores and products",
      "sectionDescription": "Choose public stores or exact product URLs."
    },
    "productUrls": {
      "title": "Product page URLs",
      "description": "Up to 100 public /products/handle URLs. Each must expose Shopify product Ajax JSON. Headless stores without that endpoint are unsupported.",
      "type": "array",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "maxItems": 100
    },
    "maxProductsPerStore": {
      "title": "Maximum products per store",
      "description": "Maximum distinct catalog products to inspect per store. All available variants of selected products are considered until the saved-row cap.",
      "type": "integer",
      "minimum": 1,
      "maximum": 1000,
      "default": 5,
      "sectionCaption": "2 · Catalog and result limits",
      "sectionDescription": "Bound the product/page window separately from saved variant rows."
    },
    "maxPagesPerStore": {
      "title": "Maximum catalog pages per store",
      "description": "Maximum public catalog pages requested per store. Reaching this selected cap does not imply a complete catalog.",
      "type": "integer",
      "minimum": 1,
      "maximum": 40,
      "default": 10
    },
    "maxResults": {
      "title": "Maximum saved rows",
      "description": "Stop after this many saved rows across all sources. This is a selected sample limit, not proof of complete source coverage.",
      "type": "integer",
      "minimum": 1,
      "maximum": 10000,
      "default": 100
    },
    "comparePrevious": {
      "title": "Compare with saved observations",
      "description": "Compare against this snapshot name. First observation emits baseline rows; only successfully saved rows advance history. Run the same snapshot name sequentially.",
      "type": "boolean",
      "default": false,
      "sectionCaption": "3 · Saved comparisons",
      "sectionDescription": "The first comparison establishes history. Reuse the same scope for later observations and avoid concurrent writers."
    },
    "onlyChanges": {
      "title": "Save only new or changed observations",
      "description": "Also enables comparison. First run emits baseline rows. Later unchanged observations produce no rows; this can legitimately return an empty dataset.",
      "type": "boolean",
      "default": false
    },
    "snapshotName": {
      "title": "Saved comparison name",
      "description": "Named persistent history, isolated by source. Use 1–40 letters, digits, hyphens or underscores. Do not run the same name concurrently.",
      "type": "string",
      "editor": "textfield",
      "default": "default",
      "pattern": "^[a-zA-Z0-9][a-zA-Z0-9_-]{0,39}$"
    },
    "maxRunSeconds": {
      "title": "Run time budget (seconds)",
      "description": "Own deadline, also shortened to 25 seconds before the platform timeout. Requests and retry delays stay within the remaining time.",
      "type": "integer",
      "minimum": 30,
      "maximum": 3600,
      "default": 240,
      "sectionCaption": "4 · Advanced run settings",
      "sectionDescription": "Adjust the time budget only when a larger collection needs it."
    }
  },
  "description": "Choose public store or product URLs, bound the catalog window and optionally compare saved variants. Price, currency and availability reflect the public source response, not a checkout or stock guarantee."
}
