The grandchild process closes all inherited fds (3..maxfd) to detach
from uWSGI. session_factory().get_bind() returned a session bound to
the now-dead SQLite fd. Capture the DB URL before forking, create a
fresh engine in the child.
4 concurrent workers was overwhelming the unsandbox API (429/502).
Cap at 1 worker for now. Also increase stderr truncation from 500
to 2000 chars to see full ffmpeg errors.
Single fork left the child in the uWSGI process group with inherited
HTTP sockets, causing 502s. Double-fork with setsid() and fd cleanup
so the grandchild is fully orphaned to init.
uWSGI kills worker processes after ~60-90s ("NO MERCY!!!"), which
kills all daemon threads including the backfill. Fork a child process
that's independent of the worker — survives parent SIGKILL.
Replace threading.Lock + set guard with fcntl.flock on a lockfile
(/tmp/karaoke_backfill_{shop_id}.lock) for one-per-shop guard that
auto-releases on crash. Also add error logging for unsandbox execute
failures (was previously silent).
SVG bar charts for 28-day daily views, CSS horizontal bars for
traffic sources and device split, inline mini bars in top products
table. Zero-dep, server-side rendered, theme-aware, no JS.
add is_owner flag to PageSession — set via data attribute when
the viewer can_edit_shop. beacon stores the flag, view_count
skips increment for owners, and all analytics queries filter
owner sessions out of funnel metrics. owner sessions are still
recorded for separate analysis. existing NULL data treated as
non-owner.
Keys can have tiers changed or expire mid-run. Now:
- process_karaoke validates keys before downloading from S3
- backfill aborts cleanly if keys are invalid instead of defaulting to 1 worker
boto3 TransferManager spawns its own ThreadPoolExecutor internally,
which crashes with 'cannot schedule new futures after interpreter
shutdown' when uWSGI recycles the worker process.
Per-shop unsandbox API keys in shop settings. On upload, audio/video
products are processed through voxsplit in a zerotrust container (no
network, destroyed after use) producing both instrumentals and vocals
tracks. Background backfill with ThreadPoolExecutor sized to account
concurrency, exponential backoff retries. Player cycles Original →
Instrumentals → Vocals with toggle button or K shortcut.
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.