Replace display:none on radio inputs with visually-hidden technique
(position:absolute, opacity:0) so :checked pseudo-class updates
reliably when clicking labels. Default frequency changed to None.
grid-area: 1/1 was only on video elements, so non-video children (countdown
overlay, unmute button) could push videos into separate grid rows. Apply
grid-area: 1/1 to all children so both active and standby videos always
occupy the same x/y position with z-index layering.
Show comment form to all visitors instead of requiring sign-in first.
Anonymous users enter email + comment, verify via 6-digit OTP, then
their comment is created. Uses existing OTP infrastructure and honeypot
spam protection.
Named event handlers (onMediaEnded, onMediaLoadedMetadata, onMediaTimeUpdate)
replace anonymous functions so setupMediaEvents() can removeEventListener
on both active and standby elements before adding fresh listeners. This
prevents duplicate handlers from accumulating after each crossfade swap.
preloadNext() now tracks preloadingId to skip redundant fetches — prevents
presigned URL regeneration from invalidating already-buffered standby media.
- Give product-images, description, comments, and product-right opaque
backgrounds so they slide beneath the sticky video instead of through it
- Add 4px horizontal padding to mobile two-column layout matching nav inset
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.
Replace per-request Jaccard similarity with a precomputed circular
ordering of all public products. Greedy nearest-neighbor walk starts
from the newest product, accumulating stems to cluster similar content.
Ring reforges on product create/edit/upload when watch mode is enabled.
Frontend stores ring position, direction, and watch history in
localStorage. Queue items splice into playback without moving ring
position. Watched items dim in sidebar with progress indicator.
Bots and crawlers were creating empty cart rows on every page visit,
bloating the database (531k empty rows, 80% of all carts). Session
carts are now kept in-memory until a product is actually added, at
which point the view persists them via dbsession.add + flush.
- add_session_cart returns in-memory Cart, reuses UUID across requests
- authentication.py guards dbsession.delete for transient carts
- 17 new tests: unit, integration, and functional coverage
Stores played product IDs + timestamps in localStorage. Items played
within the last 4 hours are hidden from Up Next sidebar and skipped
by getNextItem(). Queue items always play (user-explicit). Cleans
stale entries on page load.
- Desktop non-watch: full nav bar sticks at top on scroll
- Desktop watch mode: only ribbon and logo stick, nav scrolls away
- Mobile/tablet: nothing sticky except video in watch mode
Phase 1: preloadNext() fetches JSON metadata immediately (fast API call,
no media download). Standby src set with preload=none.
Phase 2: bufferNext() triggered by timeupdate when ~30s from end of
current track. Switches standby to preload=auto and calls load() to
start real buffering for seamless DJ crossfade.
Raw stem overlap count favored products with large descriptions
(like books) that matched common words with everything. Jaccard
index (intersection/union) normalizes by total unique stems so
content size doesn't dominate the score.
The --editable install skips dependency resolution on cached CI venvs.
requirements-dev.txt is installed with --upgrade so setuptools will
actually get picked up.
Python 3.12 no longer bundles setuptools in virtual environments.
Pyramid imports pkg_resources from setuptools, causing CI to fail with
ModuleNotFoundError: No module named 'pkg_resources'.
Wraps ribbon and nav-grid in .sticky-header div inside section.main.
Desktop only (min-width: 800px) — sticks to top on scroll, eliminates
whitespace gap above sticky video. No change on mobile/tablet.
Hover-only controls are unreachable on touch devices. Removed opacity:0
hover-reveal pattern from queue add button. Added MOBILE USABILITY
guideline to CLAUDE.md: all interactive elements must be always visible.
iOS-style slider toggle, on by default, persisted in localStorage.
Moved from standalone checkbox into related-content header row.
Rebuilt on SPA navigation with rebindAutoplayToggle().
Countdown preview now anchors to bottom of media container instead of
covering the entire video. Uses backdrop-filter blur with theme-aware
semi-transparent background (light/dark). Compact single-row grid layout
with 60px thumb, title+number, and action buttons inline.
Also completes the stalled watch mode v2 changes: static content timer,
autoplay toggle persistence, audio container wrapping in hardSwap, and
watch_json endpoint accepting non-playable media types.
Content template: video back on top, title below (as before).
Both templates: br before "uploaded to" / "sold by" so the
subtitle text wraps to its own line under the title.
Rewrite watch mode from basic autoplay into a full SPA media controller:
- Fix get_related_products() with 3-tier fallback (stem overlap, same
media type, recency) so orphan products still show recommendations
- Add /watch/{id}/json endpoint for SPA navigation without page refresh
- Rewrite watch.js: SPA fetch+pushState, 14-second countdown timer,
DJ crossfade with dual media elements, queue system with sessionStorage
- Add audio support in watch mode (album art + audio player)
- Add queue "+" buttons to related content sidebar
- Add noscript/js-only progressive enhancement pattern
- Preload next media for instant transitions