Ring header restructured from flat 1fr/auto grid into two semantic rows:
- Info row: title + badges left, progress counter right
- Controls row: Reverse and Autoplay toggles right-aligned
Now-playing row shows full title (no line-clamp truncation) and product
thumbnail. JS reads og:image meta tag for current thumbnail during SPA
navigation. SPA updates for edit button, download button, file info,
comments, and canonical link. Footer and docs updates.
completeDjCrossfade was advancing ringPosition after syncRingPosition
already set it correctly at the halfway mark, causing autoplay to skip
one song ahead of what the sidebar displayed. Removed the redundant
advance — syncRingPosition handles positioning via updatePageContent.
Ring sidebar now displays a window around the current position: 3 items
behind (dimmed), the current item highlighted in blue with a play marker,
and 21 items ahead. Each item shows its signed ring offset (-3 to +21).
get_ring_related_products returns dicts with product and offset. Views
pass offset through to JS. Server-rendered template handles both formats.
When a viewer watches every item in the ring, the loop counter increments,
history resets (un-dims all items), and a +1/+2/+3 badge appears next to
the progress display. Shop owners/editors get a green "mod" badge.
Ring state: ringLoops persisted in localStorage alongside existing ring state.
Server: watch_json now returns is_mod flag. Templates pass data-watch-is-mod.
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.