{
  "$comment": "Handoff contract for the Drupal 11.4 Canvas SDC build. Generated by make-manifest.py \u2014 do not hand-edit.",
  "source": "build-manual tab 13-SDC + the LiNK prototype",
  "generated_from": "Link-Sitebuild/prototype",
  "conventions": {
    "props": "Scalars and typed arrays only. JSON Schema types as written.",
    "slots": "Anything carrying markup. Canvas rejects markup passed through a prop.",
    "ids": "No component may hardcode an id \u2014 use Html::getUniqueId(). components.py derives every id from an instance key.",
    "js": "Attach via the component's own *.libraries.yml. No inline <script>. Behaviors must be idempotent (once()), because Drupal re-runs them on AJAX.",
    "css": "Tokens only, never raw hex. Global overrides must sit in a @layer so a component's own utilities cannot be silently outranked.",
    "tokens": "css/tokens.css @theme static block lifts verbatim into src/input.css. `static` is required: it forces every token to emit even when the component in isolation references only a few."
  },
  "not_components": "The prototype marks ~79 distinct `<!-- component: X -->` names, but only the 16 above are tab-13-SDC components. The rest are page sections (hero-search, promo-band, feature-band, property-facts, listing-head, app-tabs \u2026). In Drupal those become templates, blocks or paragraph types \u2014 NOT SDCs. Triaging them is the first decision of the port, and it is not made here.",
  "components": {
    "listing-card": {
      "status": "inline",
      "library": [
        "linkFavorites"
      ],
      "props": {
        "image": "string",
        "price": "integer",
        "address": "string",
        "beds": "integer",
        "baths": "number",
        "sqft": "integer",
        "status": {
          "enum": [
            "For Sale",
            "Under Contract",
            "Sold"
          ]
        },
        "url": "string",
        "flag_buttons": "boolean"
      },
      "slots": {},
      "note": "Most reused component. Build first after tokens.",
      "instances": {
        "dashboard-buyer-saved.html": 6,
        "dashboard-seller-listings.html": 8,
        "listing-11-s-belmont-ave.html": 3,
        "listing-1204-westover-hills-blvd.html": 1,
        "listing-1214-rothsay-cir.html": 2,
        "listing-1310-bellevue-ave.html": 1,
        "listing-13905-midlothian-tpke.html": 1,
        "listing-1400-summit-ave.html": 1,
        "listing-1801-grove-ave.html": 2,
        "listing-2201-e-broad-st.html": 2,
        "listing-2416-winterfield-rd.html": 1,
        "listing-2809-hanes-ave.html": 1,
        "listing-3300-monument-ave.html": 3,
        "listing-4120-grove-ave.html": 3,
        "listing-4417-kensington-ave.html": 3,
        "listing-506-n-32nd-st.html": 2,
        "listing-5501-riverside-dr.html": 1,
        "listing-715-n-25th-st.html": 2,
        "listing-908-hanover-ave.html": 2,
        "search.html": 18,
        "styleguide.html": 3
      },
      "instance_total": 66
    },
    "photo-gallery": {
      "status": "inline",
      "library": [
        "linkGallery"
      ],
      "props": {
        "images": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "virtual_tour_url": "string"
      },
      "slots": {},
      "note": "Keyboard + swipe accessible. Lightbox uses native <dialog>.",
      "instances": {
        "bid.html": 2,
        "listing-11-s-belmont-ave.html": 2,
        "listing-1204-westover-hills-blvd.html": 2,
        "listing-1214-rothsay-cir.html": 2,
        "listing-1310-bellevue-ave.html": 2,
        "listing-13905-midlothian-tpke.html": 2,
        "listing-1400-summit-ave.html": 2,
        "listing-1801-grove-ave.html": 2,
        "listing-2201-e-broad-st.html": 2,
        "listing-2416-winterfield-rd.html": 2,
        "listing-2809-hanes-ave.html": 2,
        "listing-3010-w-clay-st.html": 2,
        "listing-3300-monument-ave.html": 2,
        "listing-4120-grove-ave.html": 2,
        "listing-4417-kensington-ave.html": 2,
        "listing-506-n-32nd-st.html": 2,
        "listing-5501-riverside-dr.html": 2,
        "listing-715-n-25th-st.html": 2,
        "listing-908-hanover-ave.html": 2,
        "styleguide.html": 1
      },
      "instance_total": 39
    },
    "facet-sidebar": {
      "status": "inline",
      "library": [
        "linkFacetedSearch",
        "linkFacets"
      ],
      "props": {
        "facets": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string"
              },
              "count": {
                "type": "integer"
              },
              "active": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "slots": {},
      "note": "Drawer on mobile. In Drupal this is Search API Facets, not a hand-built component.",
      "instances": {
        "search.html": 2,
        "styleguide.html": 1
      },
      "instance_total": 3
    },
    "map-panel": {
      "status": "inline",
      "library": [
        "linkMap"
      ],
      "props": {
        "geojson": "object",
        "center": {
          "type": "array",
          "items": {
            "type": "number"
          }
        },
        "zoom": "integer",
        "variant": {
          "enum": [
            "search",
            "mini",
            "schools"
          ]
        }
      },
      "slots": {},
      "note": "Leaflet + OSM. variant=schools is UNIMPLEMENTED. tile.openstreetmap.org is prototype-only \u2014 production needs its own tiles.",
      "instances": {
        "dashboard-seller-listing-edit.html": 1,
        "listing-11-s-belmont-ave.html": 3,
        "listing-1204-westover-hills-blvd.html": 3,
        "listing-1214-rothsay-cir.html": 3,
        "listing-1310-bellevue-ave.html": 3,
        "listing-13905-midlothian-tpke.html": 3,
        "listing-1400-summit-ave.html": 3,
        "listing-1801-grove-ave.html": 3,
        "listing-2201-e-broad-st.html": 3,
        "listing-2416-winterfield-rd.html": 3,
        "listing-2809-hanes-ave.html": 3,
        "listing-3010-w-clay-st.html": 3,
        "listing-3300-monument-ave.html": 3,
        "listing-4120-grove-ave.html": 3,
        "listing-4417-kensington-ave.html": 3,
        "listing-506-n-32nd-st.html": 3,
        "listing-5501-riverside-dr.html": 3,
        "listing-715-n-25th-st.html": 3,
        "listing-908-hanover-ave.html": 3,
        "provider-onboarding.html": 2,
        "search.html": 2,
        "styleguide.html": 3
      },
      "instance_total": 62
    },
    "compare-table": {
      "status": "inline",
      "library": [],
      "props": {
        "listings": {
          "type": "array",
          "maxItems": 4
        },
        "notes": "string"
      },
      "slots": {},
      "note": "Sticky first column + header, max 4.",
      "instances": {
        "compare.html": 2,
        "styleguide.html": 1
      },
      "instance_total": 3
    },
    "bid-panel": {
      "status": "inline",
      "library": [],
      "props": {
        "status": {
          "enum": [
            "open",
            "accepted",
            "declined",
            "expired"
          ]
        },
        "price": "integer",
        "expiration_countdown": "string",
        "actions": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "slots": {},
      "note": "expiration_countdown renders as a static deadline, not a live timer \u2014 a client-side countdown on a bidding surface invites disputes.",
      "instances": {
        "bid.html": 2,
        "dashboard-buyer-offers.html": 2,
        "listing-11-s-belmont-ave.html": 2,
        "listing-1204-westover-hills-blvd.html": 2,
        "listing-1214-rothsay-cir.html": 2,
        "listing-1310-bellevue-ave.html": 2,
        "listing-13905-midlothian-tpke.html": 2,
        "listing-1400-summit-ave.html": 2,
        "listing-1801-grove-ave.html": 2,
        "listing-2201-e-broad-st.html": 2,
        "listing-2416-winterfield-rd.html": 2,
        "listing-2809-hanes-ave.html": 2,
        "listing-3010-w-clay-st.html": 2,
        "listing-3300-monument-ave.html": 2,
        "listing-4120-grove-ave.html": 2,
        "listing-4417-kensington-ave.html": 2,
        "listing-506-n-32nd-st.html": 2,
        "listing-5501-riverside-dr.html": 2,
        "listing-715-n-25th-st.html": 2,
        "listing-908-hanover-ave.html": 2,
        "styleguide.html": 2
      },
      "instance_total": 42
    },
    "step-tabs": {
      "status": "shared",
      "library": [],
      "props": {
        "steps": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string"
              },
              "state": {
                "enum": [
                  "done",
                  "active",
                  "todo"
                ]
              }
            }
          }
        },
        "label": "string",
        "scroll": "boolean"
      },
      "slots": {},
      "note": "State is attribute-driven (data-step-state), so one markup serves both a clickable wizard and a server-rendered row. Exactly one aria-current='step'.",
      "instances": {
        "bid.html": 2,
        "dashboard-seller-listing-wizard.html": 2,
        "provider-onboarding.html": 2,
        "styleguide.html": 4
      },
      "instance_total": 10
    },
    "rating-stars": {
      "status": "shared",
      "library": [
        "provider-rating.js"
      ],
      "props": {
        "avg": "number",
        "count": "integer",
        "threshold": "integer",
        "show": {
          "enum": [
            "avg",
            "hidden",
            "input"
          ]
        }
      },
      "slots": {},
      "note": "DR-8: aggregate withheld below `threshold` (5) reviews. DISPLAY rule, not a data rule \u2014 any surface that sorts or filters by rating must honour the same threshold or the ordering leaks the withheld score.",
      "instances": {
        "dashboard-provider.html": 1,
        "provider-rating.html": 15,
        "styleguide.html": 1
      },
      "instance_total": 17
    },
    "provider-card": {
      "status": "inline",
      "library": [],
      "props": {
        "logo": "string",
        "business_name": "string",
        "category": "string",
        "rating": "number",
        "coverage": "string",
        "promoted": "boolean"
      },
      "slots": {},
      "note": "Rating sub-display must honour DR-8 via rating-stars.",
      "instances": {
        "services.html": 6,
        "styleguide.html": 3
      },
      "instance_total": 9
    },
    "schools-nearby-list": {
      "status": "inline",
      "library": [],
      "props": {
        "schools": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "distance": {
                "type": "string"
              }
            }
          }
        }
      },
      "slots": {},
      "note": "VDOE/NCES data. NO 1-10 rating badge and no review counts \u2014 those are GreatSchools' proprietary score (resolved design contradiction #2).",
      "instances": {
        "listing-11-s-belmont-ave.html": 2,
        "listing-1204-westover-hills-blvd.html": 2,
        "listing-1214-rothsay-cir.html": 2,
        "listing-1310-bellevue-ave.html": 2,
        "listing-13905-midlothian-tpke.html": 2,
        "listing-1400-summit-ave.html": 2,
        "listing-1801-grove-ave.html": 2,
        "listing-2201-e-broad-st.html": 2,
        "listing-2416-winterfield-rd.html": 2,
        "listing-2809-hanes-ave.html": 2,
        "listing-3010-w-clay-st.html": 2,
        "listing-3300-monument-ave.html": 2,
        "listing-4120-grove-ave.html": 2,
        "listing-4417-kensington-ave.html": 2,
        "listing-506-n-32nd-st.html": 2,
        "listing-5501-riverside-dr.html": 2,
        "listing-715-n-25th-st.html": 2,
        "listing-908-hanover-ave.html": 2,
        "styleguide.html": 1
      },
      "instance_total": 37
    },
    "showing-calendar": {
      "status": "inline",
      "library": [],
      "props": {
        "events": {
          "type": "array"
        },
        "mode": {
          "enum": [
            "seller",
            "buyer",
            "provider"
          ]
        }
      },
      "slots": {},
      "note": "mode=provider is UNIMPLEMENTED. Grids are generated from Python's calendar module \u2014 never hand-typed, which is how the comp's 'April 31st' happened.",
      "instances": {
        "dashboard-buyer-showings.html": 3,
        "dashboard-buyer.html": 2,
        "dashboard-provider.html": 2,
        "dashboard-seller-offers.html": 2,
        "dashboard-seller-showings.html": 4,
        "dashboard-seller.html": 2,
        "styleguide.html": 2
      },
      "instance_total": 17
    },
    "dashboard-stat-card": {
      "status": "inline",
      "library": [],
      "props": {
        "label": "string",
        "value": "string",
        "delta": "string",
        "link": "string"
      },
      "slots": {},
      "note": "delta and link are in the contract but ship nowhere yet.",
      "instances": {
        "dashboard-admin.html": 5,
        "dashboard-buyer-insights.html": 10,
        "dashboard-buyer-offers.html": 4,
        "dashboard-provider.html": 5,
        "dashboard-seller-insights.html": 2,
        "dashboard-seller-offers.html": 7,
        "dashboard-seller-showings.html": 7,
        "dashboard-seller.html": 5,
        "styleguide.html": 4
      },
      "instance_total": 49
    },
    "status-badge": {
      "status": "inline",
      "library": [],
      "props": {
        "state": {
          "enum": [
            "active",
            "pending",
            "closed",
            "alert",
            "highlight"
          ]
        },
        "size": {
          "enum": [
            "sm",
            "md"
          ]
        }
      },
      "slots": {},
      "note": "ONE canonical colour map for every status in the product \u2014 moderation, bid, showing, project. A second status palette is a defect.",
      "instances": {
        "compare.html": 3,
        "dashboard-admin-concierge.html": 10,
        "dashboard-admin.html": 16,
        "dashboard-buyer-saved.html": 6,
        "dashboard-buyer.html": 10,
        "dashboard-provider.html": 11,
        "dashboard-seller-concierge.html": 1,
        "dashboard-seller-listings.html": 16,
        "dashboard-seller-offers.html": 7,
        "dashboard-seller.html": 20,
        "developer.html": 38,
        "search.html": 18
      },
      "instance_total": 156
    },
    "doc-list": {
      "status": "inline",
      "library": [],
      "props": {
        "docs": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "date": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            }
          }
        }
      },
      "slots": {},
      "note": "type and date are in the contract but ship nowhere yet.",
      "instances": {
        "dashboard-seller-listing-edit.html": 2,
        "listing-11-s-belmont-ave.html": 2,
        "listing-1204-westover-hills-blvd.html": 2,
        "listing-1214-rothsay-cir.html": 2,
        "listing-1310-bellevue-ave.html": 2,
        "listing-13905-midlothian-tpke.html": 2,
        "listing-1400-summit-ave.html": 2,
        "listing-1801-grove-ave.html": 2,
        "listing-2201-e-broad-st.html": 2,
        "listing-2416-winterfield-rd.html": 2,
        "listing-2809-hanes-ave.html": 2,
        "listing-3010-w-clay-st.html": 2,
        "listing-3300-monument-ave.html": 2,
        "listing-4120-grove-ave.html": 2,
        "listing-4417-kensington-ave.html": 2,
        "listing-506-n-32nd-st.html": 2,
        "listing-5501-riverside-dr.html": 2,
        "listing-715-n-25th-st.html": 2,
        "listing-908-hanover-ave.html": 2,
        "styleguide.html": 1
      },
      "instance_total": 39
    },
    "message-thread": {
      "status": "shared",
      "library": [
        "messages.js"
      ],
      "props": {
        "participants": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "slots": {
        "messages": "Rendered bubbles. MARKUP -> slot, never a prop."
      },
      "note": "Sides are distinguished by three signals (alignment, ground, named author), not by alignment alone. Carries the DR-10 wire-fraud banner in every thread.",
      "instances": {
        "bid.html": 2,
        "dashboard-buyer-messages.html": 2,
        "dashboard-messages.html": 2,
        "dashboard-seller-concierge.html": 2,
        "styleguide.html": 1
      },
      "instance_total": 9
    },
    "alert-toast": {
      "status": "shared",
      "library": [],
      "props": {
        "variant": {
          "enum": [
            "success",
            "warning",
            "error",
            "info"
          ]
        },
        "headline": "string",
        "dismissible": "boolean",
        "role": {
          "enum": [
            "status",
            "alert",
            "note"
          ]
        }
      },
      "slots": {
        "body": "Message body, may contain markup. MARKUP -> slot."
      },
      "note": "Variants map onto the canonical status colours. role defaults per variant (error=alert/assertive, else status/polite); a persistent callout passes role=note and is not a live region at all.",
      "instances": {
        "bid.html": 4,
        "dashboard-seller-listing-wizard.html": 1,
        "styleguide.html": 10
      },
      "instance_total": 15
    },
    "empty-state": {
      "status": "inline",
      "library": [],
      "props": {
        "icon": "string",
        "headline": "string",
        "cta": "string"
      },
      "slots": {},
      "note": "Pairs with alert-toast in tab 13-SDC.",
      "instances": {
        "dashboard-buyer-saved.html": 2,
        "search.html": 2,
        "styleguide.html": 1
      },
      "instance_total": 5
    }
  }
}
