Add Security section to CLAUDE.md documenting both CWE-407 surfaces:
- Search/feed endpoints (fixed, commit f9cbebb)
- Bleach HTML sanitization: O(2^N) on crafted HTML, no input cap in MPS
Add docs/poc-cwe407.py: proof-of-concept timing harness covering
rbox-search, rbox-page, rbox-dump, mps-search, mps-sitemap vectors.
Authorized use only — run against own staging/dev instance.
- shop.py: strip empty tokens, cap keywords to 10 before passing to model
- product.py: add .limit(200) per keyword query — was unbounded .all()
- feeds.py: add .limit(1000) on product/content feed queries — was unbounded
Design doc for ingesting WordPress sites into MPS shops. Covers two
input modes (REST API + WXR XML), 4-phase HTML conversion pipeline,
content/media/comment mapping, CLI interface, competitive analysis,
and future enhancements. Includes rendered dot diagrams for the
architecture overview and HTML conversion detail flow.
Add docs/karaoke-pipeline.md covering the full streaming pipeline from
MPS through unsandbox API to zerotrust container and back. Includes two
Graphviz dot diagrams (rendered to SVG):
- karaoke-pipeline.dot: full system flow across MPS, API, pool, container
- karaoke-ondemand.dot: watch mode on-demand user flow
Update architecture.md feature toggle matrix and related docs table.
Update CLAUDE.md karaoke section with streaming path and on-demand info.
Karaoke button (🎤) now always visible for audio/video products when the
shop has unsandbox API keys configured. Clicking it triggers on-demand
processing via POST /karaoke/{product_id} if tracks don't exist yet.
Server forks a detached child process (survives uWSGI recycling) to run
process_karaoke, then the existing 10s watch_json refresh picks up the
new URLs when processing completes. Button shows hourglass during
processing and auto-switches to instrumentals on completion.
Changes:
- New route + view: POST /karaoke/{product_id} (on-demand processing)
- watch_json + content.py: add karaoke_eligible flag
- content.j2: data-karaoke-eligible attribute on media container
- watch.js: show button when eligible, trigger processing, detect
completion via URL refresh, auto-switch to instrumentals
- related_content.j2: mic emoji button, JS controls visibility
- CSS: disabled state for processing button
- 8 new functional tests covering eligibility, processing, edge cases
1. Crossfade race condition: when a DJ crossfade is active and the user
clicks a new song, cancelDjCrossfade resets djCrossfadeActive but the
old song is still near its end — timeupdate immediately re-triggers
startDjCrossfade, racing with the in-flight fetch. Added
navigationPending flag to block DJ crossfade and ended handler while
a user-initiated navigation is in progress.
2. getNextItem/getNextUnwatchedItem now respect media type filters.
Previously, filtering to "video only" still showed an image in the
countdown because the ring walked by position without checking
data-media-type. Now skips filtered items.
3. Countdown overlay moved from position:absolute inside the video/audio
container (covering native controls) to a flow-positioned element
between the media and title. Removed watch-countdown-static class
since all countdown instances now use the same in-flow layout.
- reforge_discovery_ring_async: add non-production guard matching sync version
- is_trial_active/is_trial_expired: check trial_ended flag
- has_primary_s3: include primary_s3_region in validation
- gift card migration downgrade: add missing json_gift_cards drop + existence guards
The related content sidebar now shows all ring items from the current
position forward, matching content.py behavior. This means the sidebar
never "runs out" of items — Fresh mode hides watched items and reveals
the full remaining ring. Items beyond offset 7 still use the
related-content-overflow CSS class for mobile.
The ring was only populated from localStorage or during SPA navigation.
First visits (empty localStorage) or cross-shop visits (stale ring)
left ringProductIds empty, causing the related content sidebar to show
only the current product. Now:
- On page load, if localStorage ring is empty, fetch from watch JSON
- Invalidate stale rings when current product is not in the saved ring
- Skip discovery ring reforge for non-production shops (MPS-14)
- Block settings POST when trial expired, except environment and
bucket settings needed for onboarding (MPS-15)
- BYOB upload enforcement is a soft prompt (trial tip in settings)
rather than hard block — new users need to upload during trial
MPS-14: Add environment column (production/staging/development) to shops.
Non-production shops excluded from feeds, search, and discovery.
Environment banner in base template. Settings UI and creation selector.
MPS-15: Add 21-day free trial with trial_started_timestamp on shop
creation. Properties: is_trial_active, is_trial_expired, trial_days_remaining,
is_active. Grandfathered pre-trial shops (NULL timestamp = paid).
Trial banner in base template.
MPS-16: BYOB (Bring Your Own Bucket) - per-shop S3 storage with
primary_s3_* columns. Shop-aware request methods (shop_uploads_client,
shop_bucket_name, shop_cdn_endpoint) that fall back to MPS default.
All templates and views updated from global to shop-aware S3 references.
Bucket settings UI in shop settings.
Migration: 9884324a48e3 (idempotent, 11 new columns on mps_shop).
Tests: 14 unit tests + 8 functional tests (712 total, all pass).
Variable-amount gift cards purchasable with any payment method.
Code-based redemption at checkout (applied to cart like coupons).
Partial use across multiple purchases, never expire. Shop owners
control min/max amounts and can disable individual cards.
Models: GiftCard, GiftCardTransaction, CartGiftCard + migration.
Views: purchase page, cart apply/remove, shop admin manage/detail/toggle.
Templates: gift_card.j2, gift_card_manage.j2, gift_card_detail.j2.
Cart integration: gift cards deduct after coupons in all checkout paths.
Tests: 10 new unit tests covering model logic (677 total pass).
Adjust login card spacing for smaller screens and add overflow hidden to main content sections. Refine logo sizing across viewport sizes to prevent it from dominating on mobile devices.
- Add white-space: nowrap to section.log-in links, .user-display-name,
.cart-and-count, .mps-button, .mps-footer-links a, .mps-footer-group-title
- Prevents sloppy word breaks on "My Account", "Cart $0.00", etc.
- Remove .well from login-card to prevent padding/bg override
- Add border-radius and border directly to .login-card
- Bump specificity on .landing-feature-card.well and .landing-cta-card.well
- Constrain hero CTA button min-width to 180px
- Add display:block to .login-form-input for reliable full-width
Move :root and [data-theme="dark"] variable blocks from common.css into
tokens.css so the entire theme system lives in one file. Replace ~38
hardcoded #2d3748 dark button backgrounds with var(--dark-button-bg).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace ~150 hardcoded color, spacing, radius, font, and border values
with CSS custom property references from tokens.css. Every replacement
uses var() with the original value as fallback, ensuring zero visual
change while establishing the token-based foundation.
Categories migrated:
- Colors: button backgrounds, link color, status text, alert bg/text/border
- Surfaces: wells, content bg, table stripes, card bg, input bg
- Borders: input borders, thumbnails, comment replies, crypto cards
- Border radius: buttons, inputs, wells, serp items, alerts, cards
- Typography: font-family, font-size, font-weight on buttons/labels
- Spacing: padding, margins on buttons, inputs, coupons, notices
- Motion: theme toggle transition uses duration/easing tokens
Dark mode [data-theme="dark"] blocks left untouched — they override
via the existing variable system and will be consolidated in a future pass.