Move market composition from Python to .sx defcomps (Phase 3)

Python sxc/pages/ functions no longer build nested sx_call chains or
reference leaf component names. Instead they extract data (URLs, prices,
CSRF, cart state) and call a single top-level composition defcomp with
pure data values. The .sx defcomps handle all component-to-component
wiring, iteration (map), and conditional rendering.

New .sx composition defcomps:
- headers.sx: ~market-header-from-data, ~market-desktop-nav-from-data,
  ~market-product-header-from-data, ~market-product-admin-header-from-data
- prices.sx: ~market-prices-header-from-data, ~market-card-price-from-data
- navigation.sx: ~market-mobile-nav-from-data
- cards.sx: ~market-product-cards-content, ~market-card-from-data,
  ~market-cards-content, ~market-landing-from-data
- detail.sx: ~market-product-detail-from-data, ~market-detail-gallery-from-data,
  ~market-detail-info-from-data
- meta.sx: ~market-product-meta-from-data
- filters.sx: ~market-desktop-filter-from-data, ~market-mobile-chips-from-data,
  ~market-mobile-filter-content-from-data, plus 6 sub-composition defcomps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 01:11:57 +00:00
parent 36a0bd8577
commit e81d77437e
12 changed files with 961 additions and 781 deletions

View File

@@ -1,9 +1,6 @@
"""Price helpers, OOB helpers, product detail/meta builders."""
"""Price helpers, OOB helpers, product detail/meta data builders."""
from __future__ import annotations
from typing import Any
from shared.sx.parser import SxExpr
from shared.sx.helpers import sx_call
@@ -54,31 +51,14 @@ def _set_prices(item: dict) -> dict:
rp_val=rp_val, rp_raw=rp_raw, rp_cur=rp_cur)
def _card_price_sx(p: dict) -> str:
"""Build price line for product card as sx call."""
pr = _set_prices(p)
sp_str = _price_str(pr["sp_val"], pr["sp_raw"], pr["sp_cur"])
rp_str = _price_str(pr["rp_val"], pr["rp_raw"], pr["rp_cur"])
parts: list[str] = []
if pr["sp_val"]:
parts.append(sx_call("market-price-special", price=sp_str))
if pr["rp_val"]:
parts.append(sx_call("market-price-regular-strike", price=rp_str))
elif pr["rp_val"]:
parts.append(sx_call("market-price-regular", price=rp_str))
inner = "(<> " + " ".join(parts) + ")" if parts else None
return sx_call("market-price-line", inner=SxExpr(inner) if inner else None)
# ---------------------------------------------------------------------------
# Product detail page content
# Product detail data extraction
# ---------------------------------------------------------------------------
def _product_detail_sx(d: dict, ctx: dict) -> str:
"""Build product detail main panel content as sx."""
from quart import url_for
def _product_detail_data(d: dict, ctx: dict) -> dict:
"""Extract product detail page data for .sx composition."""
from shared.browser.app.csrf import generate_csrf_token
from .cards import _like_button_sx
from .cards import _like_button_data
asset_url_fn = ctx.get("asset_url")
user = ctx.get("user")
@@ -91,132 +71,70 @@ def _product_detail_sx(d: dict, ctx: dict) -> str:
brand = d.get("brand", "")
slug = d.get("slug", "")
# Gallery
if images:
# Like button
like_sx = ""
if user:
like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx)
# Like button data
like_data = None
if user:
like_data = _like_button_data(slug, liked_by_current_user, csrf, ctx)
# Main image + labels
label_parts: list[str] = []
if callable(asset_url_fn):
for l in labels:
label_parts.append(sx_call(
"market-label-overlay",
src=asset_url_fn("labels/" + l + ".svg"),
))
labels_sx = "(<> " + " ".join(label_parts) + ")" if label_parts else None
# Label overlay URLs
label_urls = []
if callable(asset_url_fn):
label_urls = [asset_url_fn("labels/" + l + ".svg") for l in labels]
gallery_inner = sx_call(
"market-detail-gallery-inner",
like=like_sx or None,
image=images[0], alt=d.get("title", ""),
labels=SxExpr(labels_sx) if labels_sx else None,
brand=brand,
)
# Image data
image_data = [{"src": u, "alt": d.get("title", "")} for u in images] if images else []
# Prev/next buttons
nav_buttons = ""
if len(images) > 1:
nav_buttons = sx_call("market-detail-nav-buttons")
# Thumbnail data
thumb_data = []
if len(images) > 1:
for i, u in enumerate(images):
thumb_data.append({"title": f"Image {i+1}", "src": u, "alt": f"thumb {i+1}"})
gallery_sx = sx_call(
"market-detail-gallery",
inner=gallery_inner,
nav=nav_buttons or None,
)
# Thumbnails
gallery_parts = [gallery_sx]
if len(images) > 1:
thumb_parts = []
for i, u in enumerate(images):
thumb_parts.append(sx_call(
"market-detail-thumb",
title=f"Image {i+1}", src=u, alt=f"thumb {i+1}",
))
thumbs_sx = "(<> " + " ".join(thumb_parts) + ")"
gallery_parts.append(sx_call("market-detail-thumbs", thumbs=SxExpr(thumbs_sx)))
gallery_final = "(<> " + " ".join(gallery_parts) + ")"
else:
like_sx = ""
if user:
like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx)
gallery_final = sx_call("market-detail-no-image",
like=like_sx or None)
# Stickers below gallery
stickers_sx = ""
# Sticker items
sticker_items = []
if stickers and callable(asset_url_fn):
sticker_parts = []
for s in stickers:
sticker_parts.append(sx_call(
"market-detail-sticker",
src=asset_url_fn("stickers/" + s + ".svg"), name=s,
))
sticker_items_sx = "(<> " + " ".join(sticker_parts) + ")"
stickers_sx = sx_call("market-detail-stickers", items=SxExpr(sticker_items_sx))
sticker_items.append({"src": asset_url_fn("stickers/" + s + ".svg"), "name": s})
# Right column: prices, description, sections
pr = _set_prices(d)
detail_parts: list[str] = []
# Unit price / case size extras
extra_parts: list[str] = []
# Extras (unit price, case size)
extras = []
ppu = d.get("price_per_unit") or d.get("price_per_unit_raw")
if ppu:
extra_parts.append(sx_call(
"market-detail-unit-price",
price=_price_str(d.get("price_per_unit"), d.get("price_per_unit_raw"), d.get("price_per_unit_currency")),
))
extras.append({
"type": "unit-price",
"value": _price_str(d.get("price_per_unit"), d.get("price_per_unit_raw"),
d.get("price_per_unit_currency")),
})
if d.get("case_size_raw"):
extra_parts.append(sx_call("market-detail-case-size", size=d["case_size_raw"]))
if extra_parts:
extras_sx = "(<> " + " ".join(extra_parts) + ")"
detail_parts.append(sx_call("market-detail-extras", inner=SxExpr(extras_sx)))
extras.append({"type": "case-size", "value": d["case_size_raw"]})
# Description
desc_short = d.get("description_short")
desc_html_val = d.get("description_html")
if desc_short or desc_html_val:
desc_parts: list[str] = []
if desc_short:
desc_parts.append(sx_call("market-detail-desc-short", text=desc_short))
if desc_html_val:
desc_parts.append(sx_call("market-detail-desc-html", html=desc_html_val))
desc_inner = "(<> " + " ".join(desc_parts) + ")"
detail_parts.append(sx_call("market-detail-desc-wrapper", inner=SxExpr(desc_inner)))
return {
"images": image_data or None,
"labels": label_urls or None,
"brand": brand,
"like-data": like_data,
"has-nav-buttons": len(images) > 1,
"thumbs": thumb_data or None,
"sticker-items": sticker_items or None,
"extras": extras or None,
"desc-short": d.get("description_short") or None,
"desc-html": d.get("description_html") or None,
"sections": d.get("sections") or None,
}
# Sections (expandable)
sections = d.get("sections", [])
if sections:
sec_parts = []
for sec in sections:
sec_parts.append(sx_call(
"market-detail-section",
title=sec.get("title", ""), html=sec.get("html", ""),
))
sec_items_sx = "(<> " + " ".join(sec_parts) + ")"
detail_parts.append(sx_call("market-detail-sections", items=SxExpr(sec_items_sx)))
details_inner_sx = "(<> " + " ".join(detail_parts) + ")" if detail_parts else "(<>)"
details_sx = sx_call("market-detail-right-col", inner=SxExpr(details_inner_sx))
return sx_call(
"market-detail-layout",
gallery=SxExpr(gallery_final),
stickers=stickers_sx or None,
details=details_sx,
)
def _product_detail_sx(d: dict, ctx: dict) -> str:
"""Build product detail content — delegates to .sx defcomp."""
data = _product_detail_data(d, ctx)
return sx_call("market-product-detail-from-data", **data)
# ---------------------------------------------------------------------------
# Product meta (OpenGraph, JSON-LD)
# Product meta data extraction
# ---------------------------------------------------------------------------
def _product_meta_sx(d: dict, ctx: dict) -> str:
"""Build product meta tags as sx (auto-hoisted to <head> by sx.js)."""
def _product_meta_data(d: dict, ctx: dict) -> dict:
"""Extract product meta/SEO data for .sx composition."""
import json
from quart import request
@@ -231,36 +149,8 @@ def _product_meta_sx(d: dict, ctx: dict) -> str:
brand = d.get("brand", "")
sku = d.get("sku", "")
price = d.get("special_price") or d.get("regular_price") or d.get("rrp")
price_currency = d.get("special_price_currency") or d.get("regular_price_currency") or d.get("rrp_currency")
parts = [sx_call("market-meta-title", title=title)]
parts.append(sx_call("market-meta-description", description=description))
if canonical:
parts.append(sx_call("market-meta-canonical", href=canonical))
# OpenGraph
site_title = ctx.get("base_title", "")
parts.append(sx_call("market-meta-og", property="og:site_name", content=site_title))
parts.append(sx_call("market-meta-og", property="og:type", content="product"))
parts.append(sx_call("market-meta-og", property="og:title", content=title))
parts.append(sx_call("market-meta-og", property="og:description", content=description))
if canonical:
parts.append(sx_call("market-meta-og", property="og:url", content=canonical))
if image_url:
parts.append(sx_call("market-meta-og", property="og:image", content=image_url))
if price and price_currency:
parts.append(sx_call("market-meta-og", property="product:price:amount", content=f"{price:.2f}"))
parts.append(sx_call("market-meta-og", property="product:price:currency", content=price_currency))
if brand:
parts.append(sx_call("market-meta-og", property="product:brand", content=brand))
# Twitter
card_type = "summary_large_image" if image_url else "summary"
parts.append(sx_call("market-meta-twitter", name="twitter:card", content=card_type))
parts.append(sx_call("market-meta-twitter", name="twitter:title", content=title))
parts.append(sx_call("market-meta-twitter", name="twitter:description", content=description))
if image_url:
parts.append(sx_call("market-meta-twitter", name="twitter:image", content=image_url))
price_currency = (d.get("special_price_currency") or d.get("regular_price_currency")
or d.get("rrp_currency"))
# JSON-LD
jsonld = {
@@ -282,6 +172,21 @@ def _product_meta_sx(d: dict, ctx: dict) -> str:
"url": canonical,
"availability": "https://schema.org/InStock",
}
parts.append(sx_call("market-meta-jsonld", json=json.dumps(jsonld)))
return "(<> " + " ".join(parts) + ")"
return {
"title": title,
"description": description,
"canonical": canonical or None,
"image-url": image_url or None,
"site-title": ctx.get("base_title", ""),
"brand": brand or None,
"price": f"{price:.2f}" if price and price_currency else None,
"price-currency": price_currency if price else None,
"jsonld-json": json.dumps(jsonld),
}
def _product_meta_sx(d: dict, ctx: dict) -> str:
"""Build product meta tags — delegates to .sx defcomp."""
data = _product_meta_data(d, ctx)
return sx_call("market-product-meta-from-data", **data)