diff --git a/CLAUDE.md b/CLAUDE.md index 5b4ed1a..e57d55e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,6 +20,13 @@ Files are NEVER streamed through uwsgi. The server only generates presigned URLs - **Uploads**: presigned `post` → client uploads directly to Spaces - **Thumbnails**: public CDN URLs with `?ts=` cache busting +**BYOB (Bring Your Own Bucket)**: Shops can configure their own S3-compatible bucket (`bucket-settings` form section). When enabled, all presigned URLs and CDN references use the shop's bucket. Always use shop-aware request methods in views and templates: +- `request.shop_uploads_client` — S3 client (shop's or MPS default) +- `request.shop_bucket_name` — bucket name (shop's or MPS default) +- `request.shop_cdn_endpoint` — CDN URL (shop's or MPS default) + +**NEVER** use `request.app["bucket.secure_uploads"]`, `request.app["bucket.secure_uploads.get_endpoint"]`, or `request.secure_uploads_client` directly in views or templates. These are only used internally by `request_methods.py` as fallbacks. + ### Karaoke Pipeline (lib/karaoke.py) Disk-backed vocal isolation pipeline. Downloads media from S3, builds a JSON @@ -69,6 +76,12 @@ This project uses a Makefile for most development operations. Use `make` command - `make_post_sell/views/cart.py` - Cart and checkout logic - `development.ini` - Configuration file +### Design System Files +- `static/css/tokens.css` — Design tokens (colors, typography, spacing, shape, elevation, motion, z-index), base resets, utility classes, animations. Single source of truth. Light mode `:root`, dark mode `[data-theme="dark"]`. +- `static/css/common.css` — Component styles consuming tokens via `var(--token, fallback)`. +- `templates/styleguide.j2` — Live component reference at `/styleguide` (view: `views/misc.py:23`). +- `docs/design-system.md` — Full design system reference doc (token tables, architecture diagram, conventions). + ## Testing Notes The project uses pytest with unittest framework. There are three types of tests: @@ -94,10 +107,12 @@ env/bin/py.test make_post_sell/tests/test_functional.py # Functional tests env/bin/py.test --cov=make_post_sell.models.cart --cov-report=term-missing make_post_sell/tests/test_models.py::TestCart ``` -### Current Coverage +### Current Coverage (712 tests) - Cart model unit tests cover critical business logic like `requires_payment` threshold (64 cents) -- Integration tests verify the original AttributeError defect fix for free coupon checkout -- Functional tests provide end-to-end coverage of cart/checkout/payment flows +- Shop environment, trial, and BYOB model properties (TestShopEnvironment, TestShopTrial, TestShopBYOB) +- Gift card model unit tests (generation, validation, transactions) +- Integration tests verify free coupon checkout, gift card flows, and multi-model interactions +- Functional tests cover cart/checkout/payment, gift card settings, environment settings, bucket settings ## Database Location @@ -232,6 +247,10 @@ Always use `uuid_str` when you need a string copy of the identifier. Models inhe **CSS LAYOUT REQUIREMENTS**: This project uses CSS Grid exclusively for layout. NEVER use Flexbox (flex) for layout. Always use CSS Grid properties for positioning and alignment. +**DESIGN TOKENS**: All new styles must consume tokens from `tokens.css` — never hardcode colors, spacing, radii, shadows, or font sizes. Use `var(--token-name)` or `var(--token-name, fallback)`. The token scale uses a 4px spacing base and major third (1.250) type scale. + +**STYLEGUIDE**: When creating new UI components (buttons, wells, alerts, layout patterns, etc.), add a live example to `/styleguide` (`make_post_sell/templates/styleguide.j2`). The styleguide is the single source of truth for the component library. If it's not in the styleguide, it doesn't exist as a pattern. + **CSS MEDIA SIZING**: Never combine `width: 100%` with `max-height` on media elements (img, video). `width: 100%` forces the element to span the full container even when `max-height` constrains the rendered content, creating dead whitespace. Use `width: auto` + `max-width: 100%` + `max-height` instead — the element shrinks to match the actual content aspect ratio within both constraints. **MOBILE USABILITY**: Never use hover-only interactions (`:hover` to reveal controls, `opacity: 0` with hover reveal, etc.). Mobile/touch devices have no hover state — controls hidden behind hover are invisible and unreachable. All interactive elements (buttons, toggles, links) must be always visible and tappable. Design touch-first, then optionally enhance for desktop hover. @@ -250,8 +269,25 @@ Elements that must stay in sync: CTA edit button, download button, comment form Disabling or removing tests weakens the codebase and is unacceptable. Tests are critical safety nets that prevent regressions. +**MANDATORY TEST COVERAGE**: Every new feature, model property, view handler, or form section MUST have tests across all three layers: +- **Unit tests** (`test_models.py`) — Test new model properties, methods, and business logic in isolation using `mock.patch`. No DB required. +- **Integration tests** (`test_integration.py`) — Test interactions between models, especially multi-model workflows (e.g., cart + coupon + gift card). +- **Functional tests** (`test_functional.py`) — Test through the web interface using `webtest.TestApp`. Cover settings form POSTs, page loads, flash messages, and DB state changes. + +If a feature touches all three layers (model + view + template), it needs tests in all three files. No exceptions. Untested code is incomplete code. + **AUTO-PUSH**: When you write new tests to cover new code paths and the full test suite passes, commit and push without asking. Bump GIT_HASH after pushing. +## Post-Work Chores + +After completing a feature or significant change, always perform these chores before considering the work done: + +1. **Tests** — Write unit tests (`test_models.py`), integration tests (`test_integration.py`), and functional tests (`test_functional.py`) covering the new code paths. All three layers are required for new features. +2. **Docs** — Update `docs/architecture.md` (feature toggle matrix, ticket index, diagrams) and `docs/design-system.md` (new components/sections) to reflect the change. +3. **Portal** — Update the marketing site at `~/git/www.makepostsell.com` (feature cards in `index.html`, includes list in `pricing.html`) when a user-facing feature is added. +4. **CLAUDE.md** — Update this file if the change introduces new patterns, form sections, model columns, or conventions that future work needs to know about. +5. **Commit & push** — Per AUTO-PUSH, commit and push when tests pass. Bump GIT_HASH. + ## Commit Message Guidelines **CRITICAL**: Do not include Claude Code attribution in commit messages. Attributing human work to Claude is inappropriate and misrepresents the actual authorship of the code. All code changes should be attributed to the human developer who reviewed, approved, and committed the work. diff --git a/GIT_HASH b/GIT_HASH new file mode 100644 index 0000000..a08c50a --- /dev/null +++ b/GIT_HASH @@ -0,0 +1 @@ +e2169ec \ No newline at end of file diff --git a/docs/architecture.md b/docs/architecture.md index dae60bd..1368f7a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -38,6 +38,7 @@ │ models │ │ media │ │ Stripe │ │ │ │ sessions│ │ thumbs │ │ PayPal │ │ Presigned │ │ signals │ │ assets │ │ Crypto │ │ URLs only │ +│ │ │ │ │ Gift Cards │ │ │ └─────────┘ └────────────┘ └──────────────┘ └───────────────┘ ``` @@ -160,6 +161,19 @@ mps_page_session (raw rows) │ Backfill on setup │ └─────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Shop Primary Bucket (BYOB) │ +│ (shop.has_primary_s3 — MPS-16) │ +│ │ +│ When enabled, REPLACES MPS Main Bucket for this shop: │ +│ - All presigned URLs use shop's S3 client │ +│ - All CDN URLs use shop's cdn_endpoint │ +│ - request.shop_uploads_client / shop_bucket_name / │ +│ shop_cdn_endpoint fall back to MPS default when off │ +│ │ +│ Configured via bucket-settings form section │ +└─────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────┐ │ User Artifact Bucket │ │ (user.has_s3_bucket) │ @@ -185,7 +199,11 @@ mps_page_session (raw rows) | Stripe | `shop.stripe_enabled` | `stripe-settings` | On | | PayPal | `shop.paypal_*` | `paypal-settings` | Off | | Crypto | `shop.monero_*` / `shop.dogecoin_*` | `crypto-settings` | Off | +| Gift cards | `shop.gift_card_enabled` | `gift-card-settings` | Off | | S3 mirror | `shop.mirror_s3_*` | `mirror-settings` | Off | +| BYOB (primary S3) | `shop.primary_s3_*` | `bucket-settings` | Off | +| Environment | `shop.environment` | `environment-settings` | 0 (production) | +| Trial | `shop.trial_started_timestamp` | Auto on creation | 21 days | | Discovery ring | `shop.discovery_ring` | Automatic | Auto-computed | | Subscriptions | `shop.subscription_*` | `ribbon-settings` | Off | @@ -203,3 +221,19 @@ mps_page_session (raw rows) | [MPS-7](tickets/mps-7.md) | Sandbox Mode — Creative Filter System | Complete | | [MPS-8](tickets/mps-8.md) | User S3 Bucket + Artifact Storage | Complete | | [MPS-9](tickets/mps-9.md) | Shop S3 Mirror Bucket | Complete | +| [MPS-10](tickets/mps-10.md) | Gift Card — Models & Migration | Complete | +| [MPS-11](tickets/mps-11.md) | Gift Card — Purchase Flow | Complete | +| [MPS-12](tickets/mps-12.md) | Gift Card — Redemption at Checkout | Complete | +| [MPS-13](tickets/mps-13.md) | Gift Card — Shop Admin & Settings | Complete | +| [MPS-14](tickets/mps-14.md) | Shop Environment — Dev & Stage Shops | Complete | +| [MPS-15](tickets/mps-15.md) | 21-Day Free Trial | Complete | +| [MPS-16](tickets/mps-16.md) | Bring Your Own Bucket (BYOB) | Complete | + +## Related Docs + +| Doc | Purpose | +|-----|---------| +| [Design System](design-system.md) | Design tokens, CSS architecture, component library | +| [JavaScript](JAVASCRIPT.md) | Client-side JS architecture | +| [Sandbox Mode](sandbox-mode.md) | Creative filter system | +| [Testing Performance](testing-performance.md) | Test suite optimization | diff --git a/docs/design-system.md b/docs/design-system.md new file mode 100644 index 0000000..ba65df1 --- /dev/null +++ b/docs/design-system.md @@ -0,0 +1,309 @@ +# MPS Design System + +## Overview + +Make Post Sell uses a design token architecture with CSS custom properties as the single source of truth. Mobile-first. CSS Grid only (no Flexbox). Accessible. Respects reduced motion. Supports light and dark themes. + +## Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────┐ +│ tokens.css │ +│ Design Tokens (:root) │ +│ │ +│ Colors · Typography · Spacing · Shape · Elevation │ +│ Motion · Z-index · Layout · State overlays │ +│ │ +│ [data-theme="dark"] overrides │ +│ │ +│ Base resets · Focus · Selection │ +│ Typography utilities (.type-*) │ +│ Elevation utilities (.elevation-*) │ +│ Surface utilities (.surface-*) │ +│ State layer · Ripple effect │ +│ Animations · Skeleton loading · Spinner │ +│ Spacing utilities (.mt-*, .mb-*, .p-*, .gap-*) │ +│ Container utilities (.container, -narrow, -wide) │ +└──────────────────────┬──────────────────────────────────┘ + │ consumed by + ▼ +┌─────────────────────────────────────────────────────────┐ +│ common.css │ +│ Component Styles │ +│ │ +│ Body layout · Navigation · Forms · Buttons │ +│ Product grid · Cards · Cart · Comments │ +│ Wells · Alerts · Ribbons · Footer │ +│ Watch mode · Landing page · Login │ +│ Responsive breakpoints │ +│ │ +│ References tokens via var(--token-name, fallback) │ +└──────────────────────┬──────────────────────────────────┘ + │ rendered in + ▼ +┌─────────────────────────────────────────────────────────┐ +│ templates/ │ +│ Jinja2 Templates │ +│ │ +│ base.j2 → theme toggle, nav, footer │ +│ styleguide.j2 → live component reference │ +│ product.j2, content.j2, shop.j2, etc. │ +└─────────────────────────────────────────────────────────┘ +``` + +## File Map + +| File | Lines | Purpose | +|------|-------|---------| +| `static/css/tokens.css` | 775 | Design tokens, utilities, animations, resets | +| `static/css/common.css` | 3879 | Component styles consuming tokens | +| `templates/styleguide.j2` | 1083 | Live styleguide at `/styleguide` | +| `views/misc.py:23` | — | Styleguide view controller | + +## Token Categories + +### Colors + +``` +Brand Surface Text Border +────────── ────────── ────────── ────────── +--color-green --surface-base --text-primary --border-default +--color-blue --surface-dim --text-body --border-light +--color-navy --surface-container --text-secondary --border-focus +--color-purple --surface-container- --text-muted --border-error +--color-danger high --text-faint +--color-gold --surface-inverse --text-disabled + --text-inverse +``` + +### Typography Scale (Major Third 1.250) + +``` +Token Size Use +────────── ────── ────────────────────── +--text-xs 12px Captions, overlines +--text-sm 14px Labels, small body +--text-base 16px Body text (root) +--text-md 18px Large body +--text-lg 20px Titles +--text-xl 24px Title large +--text-2xl 30px Headline 3 +--text-3xl 36px Headline 2 +--text-4xl 48px Headline 1 +--text-5xl 60px Display +``` + +### Spacing Scale (4px base) + +``` +Token Value +────────── ────── +--space-0 0 +--space-1 4px +--space-2 8px +--space-3 12px +--space-4 16px +--space-5 20px +--space-6 24px +--space-8 32px +--space-10 40px +--space-12 48px +--space-16 64px +--space-20 80px +--space-24 96px +``` + +### Shape (Border Radius) + +``` +--radius-none 0 Sharp corners +--radius-sm 4px Inputs, code blocks +--radius-md 8px Cards, buttons +--radius-lg 12px Modals, panels +--radius-xl 16px Large surfaces +--radius-2xl 24px Pills +--radius-full 9999px Circles +``` + +### Elevation (Box Shadow) + +``` +--elevation-0 none Flat +--elevation-1 subtle Cards at rest +--elevation-2 low Raised cards +--elevation-3 medium Dropdowns +--elevation-4 high Modals +--elevation-5 highest Popovers +``` + +### Motion + +``` +Durations Easing +────────────────── ────────────────── +--duration-instant 50ms --ease-standard general transitions +--duration-fast 100ms --ease-decelerate entrances +--duration-normal 200ms --ease-accelerate exits +--duration-slow 300ms --ease-emphasize emphasis +--duration-slower 400ms --ease-spring playful bounce +--duration-entrance 250ms +--duration-exit 200ms +``` + +### Z-Index Scale + +``` +--z-base 0 Default stacking +--z-dropdown 100 Dropdowns, popovers +--z-sticky 200 Sticky headers +--z-overlay 300 Overlays, backdrops +--z-modal 400 Modals +--z-toast 500 Toast notifications +--z-ribbon 600 Shop ribbon banner +``` + +### Layout Breakpoints + +``` +--content-narrow 400px Login forms, narrow content +--content-width 800px Default content width +--content-wide 1200px Wide layouts +--bp-tablet 800px Tablet breakpoint +--bp-desktop 1200px Desktop breakpoint +``` + +## Theme System + +Light mode is default (`:root`). Dark mode activates via `[data-theme="dark"]` on the `` element. The dark theme overrides all semantic tokens — surfaces, text, borders, brand colors — so components adapt automatically without per-component dark mode rules. + +``` +Light Dark +────────────────────── ────────────────────── +--surface-base: #FFFFFF --surface-base: #0d1117 +--text-primary: #333333 --text-primary: #ffffff +--border-default: #e0e0e0 --border-default: #7ab9ff +--color-green: #a3c765 --color-green: #08e700 +--color-navy: #5871ad --color-navy: #7ab9ff +``` + +Theme toggle: `window.setTheme('dark')` / `window.setTheme('light')`. + +## Typography Utilities + +CSS classes that compose token values into complete type styles: + +| Class | Size | Weight | Use | +|-------|------|--------|-----| +| `.type-display` | clamp(36px, 5vw, 60px) | bold | Hero headlines | +| `.type-headline-1` | 48px | bold | Page titles | +| `.type-headline-2` | 36px | bold | Section titles | +| `.type-headline-3` | 30px | bold | Subsection titles | +| `.type-title-lg` | 24px | semibold | Card titles | +| `.type-title` | 20px | semibold | List titles | +| `.type-title-sm` | 16px | semibold | Small titles | +| `.type-body-lg` | 18px | regular | Lead paragraphs | +| `.type-body` | 16px | regular | Body text | +| `.type-body-sm` | 14px | regular | Secondary text | +| `.type-label-lg` | 14px | semibold | Form labels | +| `.type-label` | 12px | semibold, uppercase | Overline labels | +| `.type-caption` | 12px | regular | Captions | +| `.type-overline` | 11px | bold, uppercase | Section overlines | +| `.type-code` | 0.9em | mono | Inline code | + +## Component Library + +All components are documented with live examples at `/styleguide`. The styleguide is the single source of truth for the component library. If it is not in the styleguide, it does not exist as a pattern. + +### Styleguide Sections + +| Section | ID | Description | +|---------|-----|------------| +| Tokens | `#tokens` | Raw token reference table | +| Colors | `#colors` | Brand, surface, text, border, alert swatches | +| Typography | `#typography` | Type scale and utility classes | +| Elevation | `#elevation` | Shadow levels | +| Motion | `#motion` | Animations, transitions, easing | +| Spacing | `#spacing` | Spacing scale visualization | +| Shape | `#shape` | Border radius samples | +| States | `#states` | Interactive state layers | +| Loading | `#loading` | Skeleton and spinner patterns | +| Buttons | `#buttons` | Button variants (green, blue, red, navy, outline) | +| Forms | `#forms` | Input fields, textareas, selects | +| Wells | `#wells` | Content wells and containers | +| Alerts | `#alerts` | Success, info, warning, danger alerts | +| Status | `#status` | Status indicators | +| Product Cards | `#cards` | Product grid cards | +| Cart | `#cart` | Cart and checkout components | +| Gift Cards | `#gift-cards` | Gift card purchase, balance check, management | +| Comments | `#comments` | Comment form and list | +| Toggle | `#toggle` | Toggle switches | +| Ribbon | `#ribbon` | Shop ribbon banner | +| Environment Banner | `.environment-banner` | Staging/dev environment indicator | +| Trial Banner | `.trial-banner` | Trial countdown and expiry notice | +| Task Bar | `#taskbar` | Task bar component | +| Layout | `#layout` | Grid layout patterns | +| Theme System | `#theme` | Theme toggle and dark mode | +| Footer | `#footer` | Footer component | + +## CSS Conventions + +### Layout + +- **CSS Grid only** — never use Flexbox for layout +- Mobile-first: base styles target mobile, `@media` queries enhance for larger screens +- Primary breakpoint: `max-width: 800px` for mobile + +### Token Consumption + +Components in `common.css` reference tokens with fallbacks: + +```css +/* Good — token with fallback for resilience */ +background-color: var(--surface-base, #ffffff); +border-radius: var(--radius-sm, 4px); + +/* Good — token without fallback (tokens.css always loaded) */ +padding: var(--space-4); +``` + +### Media Sizing + +Never combine `width: 100%` with `max-height` on media elements. Use: + +```css +/* Correct */ +width: auto; +max-width: 100%; +max-height: 33vh; + +/* Wrong — creates dead whitespace */ +width: 100%; +max-height: 33vh; +``` + +### Mobile Usability + +Never use hover-only interactions. All interactive elements must be always visible and tappable. Design touch-first, then optionally enhance for desktop hover. + +### Reduced Motion + +All animations respect `prefers-reduced-motion: reduce` via a global media query in `tokens.css` that collapses durations to near-zero. + +### Capability-Driven Presentation + +Follow Russell Ballestrini's capability-driven presentation practice. A page need not look identical across all browsers. Accommodate what the user's browser can do. Use the `js-only` / ` {% include 'snippets/ribbon.j2' %} +{% if request.shop and request.shop.is_non_production %} +
+ {{ request.shop.environment_label|upper }} ENVIRONMENT — This shop is not visible to the public. +
+{% endif %} +{% if request.shop and request.shop.is_trial_active %} +
+ Trial: {{ request.shop.trial_days_remaining }} day{{ 's' if request.shop.trial_days_remaining != 1 else '' }} remaining — Choose a plan +
+{% elif request.shop and request.shop.is_trial_expired %} +
+ Trial expired — Choose a plan to continue editing +
+{% endif %}
+ {% if not cart.is_empty %}
@@ -275,6 +311,16 @@ {% endif %} +
+

Gift Card

+
+ {% include "snippets/csrf.j2" %} + + +
+
+
+
Continue shopping
diff --git a/make_post_sell/templates/content.j2 b/make_post_sell/templates/content.j2 index c993fbc..79e570a 100644 --- a/make_post_sell/templates/content.j2 +++ b/make_post_sell/templates/content.j2 @@ -10,11 +10,11 @@ {%- if "thumbnail1" in product.extensions %} - + - + {%- endif %} {# Removed auto-refresh timer - better UX to let download links expire than interrupt reading #} @@ -39,7 +39,7 @@ {% set audio_extensions = ["mp3", "wav", "ogg", "m4a", "flac", "aac", "opus"] %} {% if request.shop.watch_mode_enabled and product.extensions.get("product") in video_extensions %} {# Watch mode: direct video render with autoplay #} - {% set watch_video_url = request.app["bucket.secure_uploads.get_endpoint"] ~ "/" ~ product.s3_path ~ "/product" %} + {% set watch_video_url = request.shop_cdn_endpoint ~ "/" ~ product.s3_path ~ "/product" %}
@@ -60,11 +60,11 @@ {% elif request.shop.watch_mode_enabled and product.extensions.get("product") in audio_extensions %} {# Watch mode: audio with album art #} - {% set watch_audio_url = request.app["bucket.secure_uploads.get_endpoint"] ~ "/" ~ product.s3_path ~ "/product" %} + {% set watch_audio_url = request.shop_cdn_endpoint ~ "/" ~ product.s3_path ~ "/product" %}
{% if "thumbnail1" in product.extensions %} - + {% endif %}
+ +
+
+ +
+
+ +

Environment Settings

+ +

Non-production shops are hidden from search, feeds, and discovery. Use them to stage or test before going live.

+ +
+ + +
+ Environment + + + + + + + + + +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ +
+
+ +

Storage Bucket (BYOB)

+ +

Configure your own S3-compatible storage bucket. When enabled, all media uploads and CDN URLs will use your bucket instead of the default MPS storage.

+ + {% if request.shop.is_trial_active %} +

Trial tip: Setting up your own storage bucket during your trial ensures your media is always under your control. Any S3-compatible provider works (DigitalOcean Spaces, AWS S3, Backblaze B2, etc.).

+ {% endif %} + +
+ + + + +

+ + + + +

+ + + + +

+ + + + +

+ + + + +

+ + + + +

+ + + + +

+ + + +
+ +
+
diff --git a/make_post_sell/templates/snippets/footer.j2 b/make_post_sell/templates/snippets/footer.j2 index 7849bda..1151fd4 100644 --- a/make_post_sell/templates/snippets/footer.j2 +++ b/make_post_sell/templates/snippets/footer.j2 @@ -64,6 +64,9 @@