Commit graph

84 commits

Author SHA1 Message Date
dc79d13358
feat: MPS-24 Phase 2.5 — product page polish (description wrap + price-history toggle)
Two product-page issues surfaced while shopping printableprompts on
mobile. Both fixed in one commit since they're tightly scoped to the
product page experience.

Description text clipping the right edge on mobile:

- .content-card uses CSS Grid but its grid items had default
  min-width: auto, so they expanded to their content's intrinsic
  width — long unbreakable tokens (URLs, etc.) pushed the card
  wider than the viewport. Then .content's overflow-x: clip
  silently hid the right side instead of wrapping the text.
- Add min-width: 0 + overflow-wrap: break-word to .content-card,
  .content-card-header, .content-card-body. Add word-break:
  break-word to inner <a> / <p> so URLs hyphenate at any character.

Price history shown by default:

- The price history table (commit 1e5fe27, 2026-02-11) was always
  visible to anyone who could edit the shop. Operator feedback:
  "wait for a sale" psychology hurts conversions; shoppers
  shouldn't see a timeline of past prices.
- New Shop.show_price_history Boolean (default False, server-default
  "0") with idempotent Alembic migration c792642911e2.
- Toggle lives in the existing ribbon-settings form section.
- views/product.py (public view) + views/watch.py JSON gate the
  price_history list on the toggle. Template product.j2 also gates
  rendering as belt-and-suspenders.
- Edit page (also views/product.py:product_edit) intentionally
  remains always-on — the operator needs price audit access from
  their own admin surface regardless of the shopper-facing toggle.
- New shop matrix entry in docs/architecture.md.
- 2 new functional tests (default-off + toggle round-trip).

1090 tests passing.
2026-05-15 13:54:24 -04:00
7c227ab467
feat: MPS-24 Phase 2.4 — SPA bulk tagger + Netflix-style lanes
Two operator-workflow improvements that compound. With Phase 1+2+2.3
shipped the categorisation works; with this phase iterating on tag
suggestions feels like one fluid screen instead of a stack of POST/
redirect cycles, and the lanes home actually looks like categorised
shelves.

SPA progressive enhancement on /s/{shop_id}/tags:

- Every form (create / delete / attach / detach / apply_suggestion /
  dismiss_suggestion) still POSTs and 302-redirects without JS — the
  no-JS user path is unchanged. With JS, static/js/tag_bulk.js
  intercepts submits, POSTs via fetch with X-Requested-With:
  XMLHttpRequest, and the server returns JSON describing what
  changed. Capability-driven per CLAUDE.md.
- New _is_ajax() + _tag_ajax_response() helpers in views/shop.py
  pop the Pyramid flash queue into the JSON payload so JS can render
  toasts (.tag-flash / .tag-flash-toast / .tag-flash-{success,error,
  info}). Falls back to a full form submit if fetch() errors.
- Template gained data-tag-form="<action>" attributes for delegation
  and data-tag-row / data-suggest-row / data-product-row hooks for
  DOM mutation. Re-attach pass on inserted rows.
- 6 new functional tests cover each AJAX action plus the no-JS
  fallback (POST without the header still 302-redirects).

Netflix-style horizontal-scroll lanes:

- .tag-lane-grid is now display: grid + grid-auto-flow: column +
  grid-auto-columns: minmax(160px, 200px) + overflow-x: auto +
  scroll-snap-type: x mandatory. Each lane is visually bounded as
  a category, tiles snap on swipe.
- Tiles drop the .serp class (the auto-fit grid was fighting the
  new horizontal flow) but keep .serp-item for hover styles.
- Thumbnails inside lane tiles use width:auto + max-width:100% +
  max-height:200px per CLAUDE.md media-sizing rule.
- Mobile (≤ 800px): tiles narrow to 140-160px, swipe-friendly.
- /styleguide updated with a 5-tile lane example so future operators
  see the new pattern.

1088 tests passing.
2026-05-15 12:55:36 -04:00
81c051e3fc
feat: MPS-24 Phase 2.3 — multi-bigram supersession, apostrophe labels, top_n 100
Phase 2.2 surfaced real categories but left noise:
- Color (119), Number (100), Day (63), Room (36) — unigrams fully covered
  by multiple bigrams, but the prior supersession only considered one
  bigram at a time so "Day" stayed even though "Valentine's Day" +
  "Patrick's Day" + … collectively cover all its products.
- "Valentine Day" / "Patrick Day" labels read as typo-broken because
  apostrophes were stripped during cleaning.
- 50 candidates wasn't long-tail enough on a 481-product catalog.

Three fixes:

- Multi-bigram supersession: a unigram drops when the UNION of bigrams
  containing it covers ≥ 80% of its product set. Iterates all bigrams
  for the unigram's stem, unions their product sets, computes coverage
  once.
- Apostrophe-preserving tokeniser + stemmer: `_MD_PUNCT` no longer
  strips `'`; `_WORD` regex accepts a trailing `(?:'[a-z]+)?` so
  "valentine's" and "patrick's" survive as surface forms.
  `simple_stem` drops the apostrophe tail before suffix-stripping so
  "valentine's" stems to "valentine" — the cluster groups correctly
  while the label vote wins with the readable surface form. Stopword
  check uses the apostrophe-less base so possessives can't slip past
  the list.
- top_n default 50 → 100. CLI default also bumped.

Tested with a Valentine's/Patrick's-heavy sample: bigrams render as
"Valentine's Day", "Patrick's Day" with proper apostrophes; the bare
"Day" unigram drops because the bigrams together cover all its
products. 1080 tests passing.
2026-05-15 11:41:46 -04:00
df65536c17
feat: MPS-24 Phase 2.2 — bigrams + title-required + supersession dedup
Phase 2.1's max_share=0.4 filter only caught Students (53%); the other
four printableprompts generics (Resource / Activities / Writing /
Practice, each 30-32%) slipped through. And single-word "First" was
collapsing the real phrase "First Grade" into noise. Three compounding
fixes plus a dedup pass:

- Bigram detection: adjacent non-stopword tokens cluster as phrases.
  "Write the Room" → bigram "write room"; "First Grade Math" →
  "first grade"; "Valentine's Day Color" → "valentine day"; "Novel
  Study" → "novel study". Bigrams get 2× unigram weight per product —
  phrases out-rank single words when both cluster equally well.
- Title-required filter (min_title_share, default 0.3): candidate must
  appear in title of at least 30% of carrier products. Kills
  description-only marketing noise like "versions", "engaged",
  "offered", "during", "these", "check", "right", "well", "help",
  "time" — words that live in body copy but never in product titles.
- Expanded English stopword list (~80 → ~200): adds generic verbs
  ("see", "ask", "give", "tell", "show"), marketing fluff ("perfect",
  "best", "lovely", "amazing", "favorite"), content-medium nouns
  ("version", "sheet", "page", "draw", "line", "color", "theme",
  "graphic", "answer", "picture"), and their inflections.
- Bigram supersession: when a bigram and one of its component
  unigrams overlap ≥ 80% of products, drop the unigram. Operator sees
  "Write Room" once, not "Write" + "Room" + "Write Room" three times.

URL knobs: ?max_share=0.3 / ?max_share=1 / ?min_title=0.5 /
?min_title=0 / ?bigrams=0 / ?top_n=200. CLI: --min-title-share,
--no-bigrams flags on scripts/backfill_tags.py.

On a printableprompts-shaped fixture the new defaults surface
Write Room, Novel Study, Valentine Day as bigram phrases plus Math,
Counting, Addition, Literacy, Fall — 13 clean candidates instead of
the original 50 noisy ones.

1078 total tests passing; 5 new pure-function tests cover bigrams,
title-required filter, and supersession dedup.
2026-05-15 10:49:54 -04:00
546e85416e
feat: MPS-24 Phase 2.1 — drop shop-vocabulary stems, surface more candidates
First Phase 2 deploy surfaced the wrong candidates on
shop.printableprompts.com: Students (53%), Resource (32%), Activities
(32%), Writing (31%), Practice (30%). These are shop vocabulary —
words that describe the whole shop, not categories within it. A stem
in 53% of products gives a shopper almost no information about which
subset a product belongs to.

- lib/tag_suggest.py: new max_share filter (default 0.4). Stems whose
  product share exceeds this fraction auto-drop as shop vocabulary.
  suggest_clusters now returns (clusters, filtered_count) so the UI
  can show how many stems were filtered.
- top_n default 20 → 50 so the long tail of niche categories surfaces.
- views/shop.py: ?max_share=0.3 (stricter), ?max_share=1 (disable),
  ?top_n=200 URL knobs on the suggestions endpoint — power users tune
  in the browser without redeploying. Floats over 1.0 are interpreted
  as percentages (40 → 0.4) so the URL accepts either form.
- templates/shop_tags.j2: filtered-count hint with copy-paste tuning
  knobs ("?max_share=0.3 stricter, ?max_share=1 to disable").
- scripts/backfill_tags.py: --max-share=0.4 CLI flag.
- Tests: test_suggest_clusters_filters_shop_vocabulary +
  test_suggest_clusters_max_share_one_disables_filter. Existing pure-
  function tests pass max_share=1.0 since their tiny fixtures would
  otherwise be penalised for being small. 1067 total passing.
2026-05-15 09:51:16 -04:00
5dbbe697b6
feat: MPS-24 Phase 2 — auto-suggest tags from title + description
Operator with 481 untagged products (printableprompts.com) gets a
one-click path to a usable categorization without hand-tagging each
product. Strictly suggest-then-approve — nothing writes Tag or
ProductTag rows until the operator clicks Apply on a cluster.

- lib/tag_suggest.py: pure-function clusterer. Tokenize title (weight 3)
  + description (weight 1, capped at 100 unique tokens per product),
  strip markdown / URLs / HTML, English + per-shop stopwords, simple
  suffix-strip stemmer, group by stem, drop stems matching existing
  tag slugs, rank by product count, label each cluster with the most
  frequent original word for its stem. No new deps, no ML.
- scripts/backfill_tags.py: CLI preview + --apply for a single shop.
- views/shop.py: shop_tags gains action=apply_suggestion (creates tag +
  bulk-attaches every product in cluster) and action=dismiss_suggestion
  (adds the cluster's words to shop.tag_stopwords_json so it never
  resurfaces). ?show_suggestions=1 triggers the cluster compute.
- templates/shop_tags.j2: "Suggest categories from titles + descriptions"
  button + suggestions well with per-cluster sample titles, Apply, and
  Dismiss buttons.
- 15 new tests (11 unit over tokenize / stem / cluster + 4 functional
  over the suggest/apply/dismiss flow). 1064 total passing.

On a printableprompts-style sample the clusterer surfaces Math, Reading,
Literacy, Seasonal, Novel, Activities, Comprehension — matching what an
operator would manually pick.
2026-05-15 09:09:40 -04:00
c03ca53fb4
feat: MPS-24 — shop home page categorization (tags + chips + sectioned lanes)
Operator feedback on shop.printableprompts.com flagged our flat default
home page as the reason for considering a move to Shopify. This adds an
opt-in home_layout selector with the navigation primitives shoppers expect
from a modern catalog — fewer clicks to a relevant product.

Phase 1 shipped (default unchanged for every existing shop):

- New Tag + ProductTag models, shop-scoped, many-per-product, flat (no tree)
- Shop.home_layout (0=flat / 1=chips / 2=lanes) plus tag/lane caps, optional
  featured strip, and per-shop tag stopwords for the Phase 2 auto-tagger
- home-layout-settings form section in shop_settings.j2
- Bulk tag editor at /s/{shop_id}/tags with apply/remove per product
- Public tag detail page at /s/{shop_id}/tag/{slug} (works without JS)
- Comma-separated tag input on the product edit form
- home.j2 / shop.j2 branch on layout — chip strip for layout 1, sectioned
  lanes for layout 2, flat unchanged for layout 0
- /search results page also receives the chip strip so shoppers can narrow
  keyword results by tag
- static/js/tag_filter.js progressively enhances chip clicks into in-place
  grid filtering via data-tag-slugs — zero navigation cost, capability-driven
  fallback to ?tag= URL nav with no JS
- New chip / lane CSS in common.css — tokens only, Grid only (no flexbox)
- Live tag-chip + tag-lane examples in /styleguide under #cards
- Idempotent Alembic migration creates 2 tables + 5 shop columns with
  server_default + _table_exists / _column_exists guards
- 24 new tests across unit + functional layers (1049 total passing)
- New "Ticket Scoping — One Feature, One Ticket" rule in CLAUDE.md;
  Phase 2 (deterministic auto-tag from titles) and Phase 3 (uncloseai-
  backed ML categorization behind a kill switch) stay under this ticket
2026-05-15 08:48:25 -04:00
547cc14589
docs: notification system + design-system surface for offers/auctions
- New docs/notifications.md: schema, kind matrix, breadcrumb walk,
  per-call-site wiring, UI surfaces, read-but-not-deleted semantics,
  non-fatal design.
- docs/architecture.md feature-toggle matrix extended with
  make-an-offer (shop + per-product gate), pre-accept expiration
  window, post-accept pay window, and the always-on notification
  surface. Related-docs section now links the new notifications doc
  + the existing auction-house / make-offer state-machine docs.
- docs/design-system.md component library extended with every class
  shipped this offer/auction/notification cycle: cart-negotiation-card
  + deadline + pill, offer-pay-cta-actions row, auction-winner-pay
  well, product-add-disabled-note, shop-offers-page width override,
  notification-badge pill + row + breadcrumbs + read-fade behavior,
  billing redesign classes, and the [data-pay-deadline] tick
  convention.
2026-05-14 14:49:39 -04:00
157e6f769a
feat: bounded SSE feeds for offer & auction state machines
New text/event-stream endpoints — /o/{offer_id}/events (buyer/seller only)
and /a/{auction_id}/events (public). Each polls the row ~every 1.5s, emits
a `data: {json}` frame on connect and whenever the state-machine state
changes, sends a heartbeat comment, then closes after ~25s so the browser
EventSource reconnects — "bounded" because uWSGI is sync (~16 worker
threads) and a long-lived SSE would starve the pool. Shared helper
lib/sse.py (sse_response / event_stream); it uses its own short-lived DB
session per poll (request.dbsession is already closed by pyramid_tm by the
time the streaming generator runs). Timings come from settings
(app.sse.hold_seconds / app.sse.poll_interval_seconds; test.ini sets them
tiny so the streaming tests finish in ~0.06s).

Client: auction.js opens the EventSource and feeds each frame into its
existing applyState(); it falls back to polling /a/{id}.json every 5s
where EventSource is unavailable. offer.js opens the EventSource on the
offer page and reload()s on a state change (the whole layout depends on
state / can_act). offer.j2 carries data-offer-state. Caddy auto-detects
text/event-stream and stops buffering — no Salt change.

Tests: 4 new functional tests (both endpoints stream the right
content-type + a state frame; 404 for outsiders / unknown ids). 994 passed.
2026-05-12 21:03:50 -04:00
e97d18bccd
MPS-21: public profile page, offer-history identity, shop offers inbox, actions hub rebuild
- Offer history & offer page show the buyer's display name (User.display_name
  = the public `name` handle; `full_name` is private) linked to a profile
  page — never the email. _serialize_offer drops buyer_email; events carry
  actor_name/actor_handle/actor_id, header carries buyer_name/buyer_handle.
- New public profile page: GET /profile/{handle} (views/user.py:user_profile,
  template profile.j2). Shows gravatar (User.gravatar_url(size) — forced
  identicon unless the user opted into Gravatar), member-since, owned/edited
  shops, and a <details> "Show email" that is server-gated: only the user
  themselves, or a shop owner/editor viewing in that shop's context
  (?shop={shop_id}) when the profile user has transacted there (an offer or
  an invoice).
- New operator offers inbox: GET /s/{shop_id}/offers (@shop_editor_required,
  shop_offers.j2) — open offers first, each row links to /o/{id} and the
  buyer's profile. Reachable from /actions/view via a new "Offers" button
  (shown when shop.offer_enabled).
- /actions/view rebuilt: one flat .action-button-grid (Grid auto-fit,
  minmax(15rem,1fr)) inside a properly-padded .action-columns well — fixes
  the off-balance two-column layout and buttons overflowing the well; no
  <br> spacers. Styleguide gains profile-card and action-button-grid
  patterns.
- offer.j2: buyer name shown (linked to profile); "Buyer:"/"Seller:" message
  lines renamed "Buyer note:"/"Seller note:" to disambiguate.

Tests: 11 new functional tests (profile render + email gating, offers inbox,
actions button, styleguide). 989 passed.
2026-05-12 17:47:24 -04:00
227fc4e564
MPS-21: offer-page state notice + design-system Make-an-Offer settings form
- offer.j2 renders a state-aware notice (.offer-state-notice, .alert
  variants) above the action forms: declined / withdrawn / expired /
  accepted (+ pay-now hint for buyer) / your-turn / waiting — so the
  viewer always understands the offer state without depending on a flash
  a JS redirect would skip. _serialize_offer now exposes is_declined,
  is_withdrawn, is_expired, is_pending, is_countered, is_accepted.
- Shop-settings Make-an-Offer section restyled with the new
  .settings-form / .settings-form-grid / .settings-field /
  .settings-field-hint system (two-up grid, per-field hints, submit
  pinned right). Added a styleguide entry under #forms.
- Reworded the section blurb: auto-declined offers are NOT silent — the
  buyer is told their offer was too low; only the seller isn't pinged.
- Fixed --color-text-muted typo (→ --text-muted) on .offer-js-flash-info.

Tests: TestOfferRoutes gains 3 state-notice render tests;
TestSettingsFormStyleguide covers the styleguide + live shop-settings
markup and asserts the old wording is gone. 978 passed.
2026-05-12 16:34:21 -04:00
2cad2482b8
MPS-20/MPS-21: capability-driven presentation for auction & offer actions
Every bid/buy-now/watch and offer open/counter/accept/decline/withdraw
POST now works as a plain browser submit: flash + 302 redirect to the
auction/offer page. JSON is returned only when the request carries
X-Requested-With: XMLHttpRequest. Adds offer.js progressive-enhancement
layer (mirrors auction.js); pay-now CTA on accepted offers; .offer-js-flash
styling; grid layout for offer/action forms. offer_accept emails the
buyer only on the transition into ACCEPTED.

Tests: TestOfferRoutes/TestAuctionRoutes now drive the JSON path via an
AJAX helper; new TestOfferNoJsFallback/TestAuctionNoJsFallback cover the
plain-POST redirect path. 973 passed.
2026-05-12 12:04:19 -04:00
a90979a46c
MPS-23: single warm sending identity for transactional mail
All transactional mail now sends From app.email.sender (default
no-reply@origin.makepostsell.com) instead of per-shop no-reply@<domain>,
with the shop name (or email.from_name) as the display name. The origin
identity is DKIM-signed (d=makepostsell.com) and SPF-authorized and
relays via mx1's warm IP, so operator custom-domain shops stop getting
spam-foldered. format_from_header() builds the From; send_email() gained
a from_name kwarg. Reply-To / per-shop contact email still TODO.
2026-05-12 11:13:11 -04:00
0f19da5ba8
docs: add MPS-23 ticket — consolidated transactional sender identity 2026-05-12 11:07:38 -04:00
5e13a59412
MPS-20 + MPS-21: docs — auction-house.md, make-offer.md, architecture index
- docs/auction-house.md: MPS-20 reference — state machine, models,
  bidding logic (validate_bid, soft-close, proxy resolution),
  buy-now flow, tick scheduling, routes, cart integration, live UI,
  email notifications, test summary
- docs/make-offer.md: MPS-21 reference — state machine, models,
  shop settings, lib/offer pure validators + orchestrators, tick,
  routes, cart integration, email notifications, test summary
- docs/architecture.md: MPS-20 + MPS-21 marked Complete in ticket
  index; new docs added to Related Docs table
- CLAUDE.md: new "Auction & Make-an-Offer" section listing tables,
  cart integration, cron scripts, form sections, route registration
  rules, and pointers to the per-feature docs

Total: 943 tests pass (no code change in this commit).
2026-05-09 21:46:18 -04:00
2659ebdcbe
MPS-22: kill-switch feature flags for karaoke + torrent
Karaoke (MPS-18) and torrent (MPS-19) are broken in production. Adding
two global feature flags off by default so neither feature surfaces in
UI or accepts route traffic until they're fixed.

Pattern mirrors app.features.popout_player.enabled — reified request
properties (request.karaoke_enabled, request.torrent_enabled) read from
ini settings. Templates wrap UI in {% if %}, views return HTTPNotFound
on form sections + routes, view contexts blank out feature-specific
keys when flag off so SPA navigation does not try to render them.

test.ini sets both flags True so existing feature tests keep working.
TestKillSwitches builds a fresh app with both False and verifies the
off path: form_section POSTs return 404, settings page omits sections,
karaoke route 404s, landing page omits karaoke marketing copy.

GET /s/{shop_id}/torrent-backfill-status is shadowed by an earlier
shop_slug catch-all route in production — pre-existing routing defect
that MPS-19 needs to fix when it lands.
2026-05-09 16:51:24 -04:00
b3d9b2b39c
docs: tickets MPS-18..21 — karaoke/torrent fixes + auction/offer proposals
MPS-18: diagnose and fix broken karaoke vocal isolation pipeline.
MPS-19: diagnose and fix broken torrent / magnet link distribution.
MPS-20: propose eBay-style auction house mode (bidding, reserve,
soft-close, proxy, buy-now).
MPS-21: propose make-an-offer mode (counter/accept/decline/expire
state machine, auto-accept and auto-decline thresholds).

Each ticket carries proposal, full file list, models, state machine,
GTM plan, and unit/integration/functional test requirements.

architecture.md ticket index extended with MPS-17 (was missing) plus
the four new tickets.
2026-05-09 16:13:57 -04:00
b72fc6bf3b feat: REST API v1 — HMAC-signed product/content creation and file upload
Adds a public/private key pair authentication system and REST API endpoints
for programmatic product and content management. Designed for CI/CD pipelines
(permacomputer.com image hosting).

Auth: HMAC-SHA256 signed requests using public/private key pairs.
The secret key never travels over the wire. Replay window: 300 seconds.

Endpoints:
  POST /api/v1/products              create product (fiat/crypto priced)
  POST /api/v1/content               create content (free)
  GET  /api/v1/products/{id}         get product
  GET  /api/v1/content/{id}          get content
  POST /api/v1/products/{id}/upload-url     presigned S3 POST for direct upload
  POST /api/v1/content/{id}/upload-url      presigned S3 POST for direct upload
  POST /api/v1/products/{id}/files/confirm  confirm upload, register metadata
  POST /api/v1/content/{id}/files/confirm   confirm upload, register metadata

Key management UI in shop settings. Secret shown once on generation.

Migration: mps_api_key table (id, shop_id, public_key, secret_key, label,
created_timestamp, last_used_timestamp, is_active)

Tests: 12 MpsApiKey unit tests, 8 REST API functional tests (269 total passing)
2026-04-06 15:23:30 -04:00
389f80a730 docs: CWE-407 security section — bleach O(2^N) exposure and PoC
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.
2026-03-29 21:19:59 -04:00
01bd9086fe docs: wordpress import pipeline architecture with dot diagrams
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.
2026-03-12 11:32:47 -04:00
8649e6aaae docs: karaoke pipeline architecture with dot diagrams
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.
2026-03-11 17:49:57 -04:00
bfe2289313 docs: update CLAUDE.md testing requirements, architecture, and design system
- CLAUDE.md: add mandatory test coverage rule (all 3 layers required),
  document BYOB shop-aware S3 methods, update test count to 712
- architecture.md: mark MPS-14/15/16 complete, add environment/trial/BYOB
  to feature toggle matrix, add BYOB to S3 storage diagram
- design-system.md: add environment and trial banner components
2026-03-07 19:01:57 -05:00
add2e85248 docs: add tickets MPS-14 (dev/stage shops), MPS-15 (free trial), MPS-16 (BYOB) 2026-03-07 17:30:05 -05:00
46c1b2d521 test: add gift card integration tests; update docs and CLAUDE.md
7 integration tests for gift card models, cart integration, deduction,
transactions, coupon+gift card combo, validation, and JSON purchases.
Update architecture.md (feature toggle matrix, ticket index, diagram).
Update design-system.md (gift card component section).
Add post-work chores checklist to CLAUDE.md.
2026-03-07 17:15:00 -05:00
5d501652c2 feat: add gift card system for shops (MPS-10 through MPS-13)
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).
2026-03-07 15:38:40 -05:00
901741b2da docs: add design system reference and cross-link architecture docs
New docs/design-system.md covers token architecture, file map, token
category tables, theme system, typography utilities, component library
index, CSS conventions, and load order.

Add Related Docs section to architecture.md linking design system,
JavaScript, sandbox mode, and testing performance docs.
2026-03-06 18:22:05 -05:00
32a19a339c docs: add MPS-6 through MPS-9 tickets, architecture diagram, update JS docs
- MPS-6: referrer analytics (domain, query, trend line charts)
- MPS-7: sandbox mode creative filter system
- MPS-8: user S3 bucket + artifact storage
- MPS-9: shop S3 mirror bucket
- architecture.md: system diagram, request flow, data pipeline, S3 layout
- JAVASCRIPT.md: add sandbox.js, signals.js, MediaPipe SDK entries
- sandbox-mode.md: mark S3 upload as implemented
- mps-2.md: document referrer_domain + referrer_query columns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 06:19:56 -05:00
d5bf831bcd docs: add artifact storage section to sandbox docs
Document per-user S3 bucket configuration, upload flow diagram,
supported services, and updated file reference table.
2026-02-27 12:58:16 -05:00
9dda6ca36b docs: add sandbox mode architecture and filter reference
Architecture diagrams, export pipeline, face detection pipeline,
filter preset reference table, CORS requirements, localStorage keys,
mobile behavior, and stacking with shop color filter.
2026-02-27 11:43:10 -05:00
1e5fe27d38 Price history on product pages, Fresh toggle, media type filters for ring
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.
2026-02-11 19:22:44 -05:00
d9a565b57a Add tickets MPS-4 and MPS-5 for 502 worker recycling fix
MPS-4: Eliminate intermittent 502s via uwsgi config tuning
MPS-5: Investigate root cause of worker memory growth (~40MB/min)
2026-02-11 10:13:52 -05:00
faefd94abf Video watch metrics, per-product analytics page, SPA thumbnail sync
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
2026-02-11 09:03:29 -05:00
2f5498a1f4 deleted: docs/tickets/an-algo/01-engagement-signal-weighting.md
deleted:    docs/tickets/an-algo/02-truth-fidelity-score.md
	deleted:    docs/tickets/an-algo/05-love-chains-referrals.md
	deleted:    docs/tickets/an-algo/06-love-letters-messaging.md
	deleted:    docs/tickets/an-algo/08-pillars-score-dashboard.md
	modified:   make_post_sell/GIT_HASH
2026-02-10 16:53:31 -05:00
fca2f4a051 Isolate parallel test runs with PID-based database filenames
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.
2026-02-09 16:24:25 -05:00
3ba8802d9a Fix SQLAlchemy deprecation in async reforge, add playback progress restore, add signal gathering tickets 2026-02-09 13:52:34 -05:00
f780f0dab6 Redesign ring header into structured 2-row grid, show full now-playing title and thumbnail
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.
2026-02-09 11:46:54 -05:00
bc4858c58a Add YouTube-style watch mode with sticky video, autoplay, and related content
Shop owners can enable watch mode in settings to get: direct video autoplay
with muted fallback, sticky video player while scrolling, and a stemming-powered
"Up Next" related content sidebar. Degrades gracefully per capability.
2026-02-07 14:57:37 -05:00
68089dff5b Add AJAX comment submission to preserve media playback
Comments now submit via fetch() when JS is available, returning JSON
instead of triggering a full page reload that kills video/audio playback.
Falls back to the existing POST+redirect when JS is disabled.
2026-02-07 11:19:58 -05:00
c6ea5f7f4b Add comprehensive documentation for parallel test execution system
Documents the 16.7x test speedup achieved through pytest-xdist:
- Explains per-worker database isolation strategy
- Details SQLite WAL mode configuration for concurrency
- Describes automatic worker distribution and load balancing
- Covers implementation challenges and solutions
- Provides performance metrics and hardware requirements
- Includes best practices for parallel-safe tests

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-21 12:25:41 -05:00
42241d79b7 Remove jQuery dependency, convert to vanilla JavaScript 2025-12-22 18:34:35 -05:00
4bc0c67e3a Add JavaScript documentation 2025-12-22 18:29:10 -05:00
4210bc1421 Add Adyen payment integration
- Add Adyen API credentials to Shop model (api_key, merchant_account,
  client_key, hmac_key, enabled)
- Add adyen_psp_reference to Invoice model for payment tracking
- Add Adyen checkout views (create-session, complete-checkout)
- Add Adyen webhook handler with HMAC verification
- Add shop settings UI for Adyen credentials
- Add request.adyen_enabled and request.adyen_globally_enabled
- Update ADYEN.md with verification details and implementation status
- Add 21 tests (8 unit, 5 integration, 5 functional + 3 invoice)
2025-12-22 17:16:54 -05:00
a2e2092a31 Add privacy warnings to PayPal docs, create Adyen integration doc
PayPal:
- Document invasive KYC requirements (face scanning, government ID)
- Note that crypto is the privacy-preserving alternative

Adyen:
- Document integration approach (similar to Stripe)
- Include Python library usage, webhooks, credentials needed
- Status: not yet implemented
2025-12-22 16:11:54 -05:00
ac2f586453 Add Stripe payment tracking and webhook resilience
- Add stripe_payment_intent_id and stripe_charge_id columns to Invoice
- Store payment references during checkout for traceability
- Use idempotency key to prevent duplicate charges on retry
- Add Stripe webhook handler for payment_intent.succeeded, payment_failed,
  charge.refunded, and charge.dispute.created events
- Consolidate PayPal webhooks into webhooks.py
- Add stripe.webhook_secret configuration for signature verification

Tests: 8 unit, 5 integration, 7 functional tests for Stripe functionality
2025-12-22 16:03:20 -05:00
979c5117df Merge PayPal views into cart.py, delete paypal.py 2025-12-22 14:29:00 -05:00
8121772233 Rename and simplify PayPal docs 2025-12-22 14:21:24 -05:00
30c1e29aa8 Mark PayPal integration as released Dec 22, 2025 2:30 PM 2025-12-22 14:20:20 -05:00
261ad97c7b Remove PayPalPayment table, use Invoice columns instead
- Add paypal_order_id and paypal_capture_id columns to Invoice model
- Update migration to add columns to mps_invoice instead of creating separate table
- Remove PayPalPayment model (simpler architecture matching Stripe)
- Update paypal.py to store PayPal info directly on Invoice
- Update paypal_webhooks.py to query Invoice by paypal_order_id
- Update Invoice.payment_method property to detect PayPal payments
2025-12-22 12:51:07 -05:00
b5a32c797e Clean up PayPal docs: remove references to non-existent files 2025-12-22 12:43:35 -05:00
09e3873028 Update docs: single migration, tables auto-created 2025-12-22 12:41:45 -05:00