Chrome blocks external SVG filter references via CSS url(). Replace with
native CSS filter functions (grayscale + sepia + hue-rotate + saturate)
that work in all browsers. Delete the now-unused SVG file.
Inline SVG filter url(#id) fails when the SVG defs are inside the
filtered element (both on <html> and <body>). Data URI SVG filters
are blocked by Chrome. External SVG file at /static/svg/color-filters.svg
avoids both issues — no DOM circular reference, no security block.
Browser fetches the SVG once and caches it.
Data URI SVG filters don't work in Chrome (blocked for security).
Inline url(#id) on <html> fails due to root-element rendering order.
Fix: keep data-color-filter attribute on <html>, apply filter to <body>
via descendant selector. SVG filter defs in <body> with <defs> wrapper
resolve correctly since DOM is parsed before CSS filter is applied.
CSS filter: url(#id) on <html> fails because the SVG filter definitions
are children of the element being filtered (circular reference). Grayscale
worked because it uses native CSS filter, not SVG. Now all feColorMatrix
filters are self-contained data URIs in CSS — no DOM SVG block needed.
- Add shuffle button next to Next in SPA ring navigation
- Expand grayscale mode to full color filter system (off, grayscale, R, G, B, R+G, R+B, G+B)
using SVG feColorMatrix filters for selective channel isolation
- Move comment email field below textarea, fix email input theming for dark mode
Server renders data-grayscale="on" on <html> so it works without JS.
Toggle in Ribbon & Layout settings, off by default. Original assets
untouched — purely presentational via filter: grayscale(100%).
The .related-content-overflow display:none rule was being overridden by
.related-content-row display:grid later in the file (equal specificity,
cascade order wins). Bump specificity with .related-content-row.related-content-overflow.
Authenticated users on the SaaS domain with no shop get redirected
from / to /s/new instead of seeing a bare message. Flash messages
are preserved by skipping the redirect when peek_flash() has content.
Anonymous visitors now see a proper landing with tagline and CTA.
Clicking the + button flashes the entire related-content-row with green
background, purple box-shadow glow, and white text. Animation fades out
over 5 seconds. Repeated clicks reset the animation and add duplicates
to the queue.
Visual feedback when adding items to the queue: the "+" button
pops to 1.3x scale with a green background, then eases back.
Reflow trick allows re-triggering on repeated clicks.
Python 3.12 venv may only create bin/python3 and bin/python3.12 without
a bin/python symlink. virtualenv-clone 0.5.7 expects bin/python to exist
when introspecting the cloned venv, causing FileNotFoundError.
Product images container now uses overflow:hidden + min-width:0 to
prevent the CSS grid child from expanding beyond the viewport. Watch
video container also gets max-width:100% + min-width:0 for the same
grid overflow fix.
Shows "Comments (N)" anchor link below the purchase/download well,
visible only on mobile where comments section is further down the page.
SPA navigation updates the count dynamically.
On mobile (<800px), hide related content items beyond offset 7 to
reduce scroll. Add a "Comments (N)" link at the bottom of the related
content panel that jumps to the comments section below. Hidden on
desktop where comments are already visible in the sidebar layout.
Updated all three layers: template, watch.js SPA, and watch JSON.
Price history: expandable <details> section on sellable product pages,
gated to shop editors. SPA-synced via watch.py JSON + watch.js rebuild.
Ticket 09 for future membership-gated access.
Fresh toggle: hides already-watched ring items (display:none vs dimmed),
persisted in localStorage, so loopers see only unwatched content.
Media type filters: Video/Audio/Image/Docs/Other pill buttons below
ring controls. Toggle off a type to hide those rows from the ring.
All on by default, persisted in localStorage. Server-rendered via
Jinja macro + data-media-type attribute, JS-synced during SPA nav.
Analytics:
- Add video watch metrics section to shop dashboard (avg % played,
completion rate, seeks, rewinds, pauses, speed) with per-product
video breakdown table
- Add per-product analytics detail page at /s/{shop_id}/analytics/{product_id}
with views over time, video watch, traffic, devices, ring entries,
engagement, comment sentiment, and price history
- Product title links on shop analytics now go to per-product analytics
- Add get_sentiment_summary_for_product() helper
SPA fix:
- Update thumbnail image src and wrapping link href during DJ fade
- Clear stale extra thumbnails from previous product
- Add file_url to watch JSON for thumbnail-to-file links
Bot crawlers (AhrefsBot, SemrushBot) hitting content pages caused
jinja2.UndefinedError: 'None' has no attribute 'can_edit_shop' because
request.user is None for unauthenticated visitors.
Video/audio → static/cross-type now fades volume + opacity over 7 seconds
instead of abrupt cut. Static content (images, PDFs) fades out smoothly
during the 7-second countdown. All transitions cancelable, no abrupt stops.
When two processes run pytest simultaneously, they no longer share
test_make_post_sell_master.sqlite. Each gets its own file keyed by PID,
preventing table-exists and readonly-database errors. Also clean up
WAL/SHM journal files on exit.
The O(n²) greedy walk held the GIL for the entire computation, starving
uwsgi workers on the 2 vCPU box. time.sleep(0) in the inner loop
releases the GIL each iteration so request handling can interleave.
One beacon per page visit sent on unload (~300 bytes JSON). No cookies,
no IPs, no fingerprints. A view counts after 7 seconds of visible time.
Creators see view counts on product pages; raw data feeds future analytics.
New: PageSession model, /signals/beacon endpoint, signals.js client
collector, product view_count column with human_view_count property,
Alembic migration, SPA integration, 30 new tests (667 total, all pass).
Unit (6 tests): thread spawning, dirty bit behavior, debounce
collapse, session.expire_all on re-run, exception cleanup, missing
shop handling.
Integration (3 tests): ring persisted to DB, dirty bit picks up
latest state, private/unlisted products excluded.
Functional (5 tests): edit doesn't block, product page uses existing
ring, watch JSON never reforges, new product triggers async reforge,
no trigger when watch mode off.
When a reforge is already running, set a dirty flag so the thread
loops once more after finishing — picks up latest product state
from rapid saves without queueing multiple reforges.
Ring reforge now runs in a background thread to avoid blocking requests.
Product/content views no longer reforge inline — they use the existing ring
or fall back to related products. og:image meta tag updated during SPA
navigation so the now-playing row shows the correct thumbnail.
Long videos (>15 min) would die mid-playback when the 15-minute presigned
URL expired. Now a timer fetches a fresh URL from the watch JSON endpoint
every 7 minutes, swaps the src, and seeks back to the saved position.
Timer resets on every product transition.
Videos shorter than 7 seconds would trigger DJ crossfade immediately on
first timeupdate, fading out before they even played. Now short clips
play fully and transition instantly to the next preloaded item.
Desktop: override align-items to stretch on watch-mode grid so watch-left
fills the full row height, giving the sticky video room to stick as long
as any content is visible in either column.
Promote video fullscreen to .watch-video-container so DJ crossfade
transitions remain seamless. Use display:contents on mobile watch-left
so sticky video sticks properly. Add z-index layering for desktop
watch mode scrolling. Add sitemap.xml link to subscribe page feeds.
Desktop: added position:relative + z-index:5 to description and comments
in watch mode so they stack below the sticky video (z-index 10).
Mobile: changed watch-left to display:contents so sticky video sticks
against the full grid container instead of just the watch-left box.
Previously product-right (ring/download) was outside watch-left, ending
the sticky context and pushing the video offscreen.
The view returned empty context on GET, so the template hardcoded checked
on the None radio. Now the view looks up the logged-in user's existing
subscription and passes current_frequency to the template, which sets
checked on the correct radio button dynamically.
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.