Forge ring on first access, add /s/{shop_id}/ring.json endpoint

Existing shops had no ring until a product edit triggered it.
Now watch_json and server-rendered views forge the ring on first
access if empty. Added ring.json diagnostic endpoint to inspect
a shop's discovery ring state.
This commit is contained in:
russell@unturf.com 2026-02-09 07:19:11 -05:00
parent bc41c3481b
commit b933daffd9
5 changed files with 50 additions and 12 deletions

View file

@ -171,6 +171,7 @@ def includeme(config):
config.add_route("player", "/player/{product_id}")
config.add_route("player_json", "/player/{product_id}/json")
config.add_route("watch_json", "/watch/{product_id}/json")
config.add_route("discovery_ring_json", "/s/{shop_id}/ring.json")
config.add_route("random", "/random")
config.add_route("tv", "/tv")

View file

@ -1595,7 +1595,7 @@ textarea.markup-editor-textarea {
}
div.message-ribbon {
display: block;
display: grid;
}
/* Desktop, NOT watch mode: full nav is sticky */
@ -2620,7 +2620,6 @@ textarea {
/* Watch mode: sticky video within tall left wrapper */
.watch-mode .product-images {
position: sticky;
top: 0;
z-index: 10;
background: var(--bg-primary, #ffffff);
overflow: hidden;
@ -2758,6 +2757,18 @@ textarea {
cursor: pointer;
}
.watch-direction-label input {
display: none;
}
.watch-direction-label input:checked + .autoplay-slider {
background: var(--blue-color, #98b6fa);
}
.watch-direction-label input:checked + .autoplay-slider::after {
transform: translateX(16px);
}
.direction-label-text {
font-size: 0.85em;
color: var(--text-muted, #999);

View file

@ -74,12 +74,15 @@ def content(request):
related_products = []
if product.shop.watch_mode_enabled:
from ..models.product import get_ring_related_products, get_related_products
from ..models.shop import reforge_discovery_ring
ring = product.shop.discovery_ring
if not ring:
ring = reforge_discovery_ring(product.shop)
request.dbsession.flush()
if ring:
from ..models.product import get_ring_related_products
related_products = get_ring_related_products(product, ring)
else:
from ..models.product import get_related_products
related_products = get_related_products(product)
return {

View file

@ -94,12 +94,14 @@ def product(request):
related_products = []
if product.shop.watch_mode_enabled:
from ..models.product import get_ring_related_products, get_related_products
ring = product.shop.discovery_ring
if not ring:
ring = reforge_discovery_ring(product.shop)
request.dbsession.flush()
if ring:
from ..models.product import get_ring_related_products
related_products = get_ring_related_products(product, ring)
else:
from ..models.product import get_related_products
related_products = get_related_products(product)
return {

View file

@ -1,7 +1,7 @@
from pyramid.view import view_config
from ..models.product import get_media_type, get_related_products, get_ring_related_products
from ..models.shop import reforge_discovery_ring
from ..models.shop import reforge_discovery_ring, get_shop_by_id
@view_config(route_name="watch_json", renderer="json")
@ -76,13 +76,15 @@ def watch_json(request):
p_or_c = "p" if product.is_sellable else "c"
canonical_url = f"/{p_or_c}/{product.id}/{product.slug}"
# Related products via discovery ring (fallback to stem-based)
# Related products via discovery ring (forge on first access if needed)
ring = request.shop.discovery_ring
if not ring:
ring = reforge_discovery_ring(request.shop)
request.dbsession.flush()
if ring and str(product.id) not in ring:
ring = reforge_discovery_ring(request.shop)
request.dbsession.flush()
if ring:
if str(product.id) not in ring:
# Product missing from ring — reforge inline
ring = reforge_discovery_ring(request.shop)
request.dbsession.flush()
related = get_ring_related_products(product, ring)
else:
related = get_related_products(product)
@ -119,3 +121,22 @@ def watch_json(request):
"related": related_data,
"ring": ring,
}
@view_config(route_name="discovery_ring_json", renderer="json")
def discovery_ring_json(request):
"""Public JSON endpoint to inspect the discovery ring for a shop."""
shop_id = request.matchdict.get("shop_id")
shop = get_shop_by_id(request.dbsession, shop_id)
if not shop:
request.response.status_int = 404
return {"error": "Shop not found"}
ring = shop.discovery_ring
return {
"shop_id": str(shop.id),
"shop_name": shop.name,
"watch_mode_enabled": shop.watch_mode_enabled,
"ring_length": len(ring),
"ring": ring,
}