diff --git a/docs/tickets/mps-24.md b/docs/tickets/mps-24.md index 2d84980..330a856 100644 --- a/docs/tickets/mps-24.md +++ b/docs/tickets/mps-24.md @@ -319,6 +319,35 @@ picker is Phase 2). | `tests/test_models.py` | `TestTagSuggestPureFunctions` — 11 unit tests over tokenize / stem / cluster | | `tests/test_functional.py` | `test_suggest_clusters_renders_candidates`, `test_apply_suggestion_creates_tag_and_attaches_products`, `test_dismiss_suggestion_adds_to_stopwords`, `test_apply_suggestion_rejects_empty_input` | +### Phase 2.4 — SPA bulk tagger + Netflix-style lanes (shipped 2026-05-15) + +Two improvements that compound for the operator workflow: + +- **SPA progressive enhancement on `/s/{shop_id}/tags`**. Each form + (create / delete / attach / detach / apply_suggestion / + dismiss_suggestion) still POSTs and 302-redirects without JS, but + with JS, `static/js/tag_bulk.js` intercepts the submit, sends + `X-Requested-With: XMLHttpRequest`, and the server returns JSON + describing what changed. JS mutates the DOM in place — no full + reload while the operator iterates on suggestions, applies a + cluster, deletes a tag they don't like, repeats. Flash messages + render as toasts via the new `.tag-flash` region. Falls back to + full submit if `fetch()` errors. +- **Netflix-style horizontal-scroll lanes**. `.tag-lane-grid` is now + a horizontal-scrolling row of fixed-width tiles + (`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, mobile-friendly. + Tiles drop the `.serp` class (the old auto-fit grid layout was + fighting the new horizontal flow) but keep `.serp-item` for hover + styles. Thumbnails: `width: auto; max-width: 100%; max-height: + 200px` per CLAUDE.md media-sizing rule. +- **Companion `serp-thumbnail` fix** (commit `e284f88`): `img.serp-thumbnail` + gained `width: auto; max-width: 100%; height: auto`. On + printableprompts the 1080×1080 natural thumbnails were forcing + grid cells wider than the column template, collapsing + `auto-fit, minmax(160px, 1fr)` to a one-column-per-viewport layout. + ### Phase 2.3 — multi-bigram supersession + apostrophe labels + top_n 100 (shipped 2026-05-15) Phase 2.2 surfaced real categories but left residue: `Color` (119), diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index 13f4e15..0eed861 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -1320,6 +1320,12 @@ div.serp-item { padding: var(--space-1, 4px); grid-column: span 1; grid-row: span 1; + /* Grid items default to min-width: auto — a wide img inside would + push the cell past minmax(160px, 1fr) and collapse the auto-fit + grid to one full-viewport column. Force min-width: 0 so the + child img's max-width: 100% actually clamps. Same gotcha as the + lane tiles below. */ + min-width: 0; /* The new CSS animations are off the hook. */ transition: background-color 800ms ease; @@ -1424,7 +1430,8 @@ a.tag-chip-active:hover { background: var(--dark-button-bg-hover, #3a4658); } -/* Sectioned-lanes layout — one lane per top tag. */ +/* Sectioned-lanes layout — one lane per top tag. Netflix-style + horizontal row with scroll-snap. */ section.tag-lane { margin: 0 0 var(--space-6, 24px) 0; } @@ -1450,6 +1457,61 @@ a.tag-lane-more:hover { text-decoration: underline; } +div.tag-lane-grid { + /* Horizontal scrolling row of fixed-width tiles. grid-auto-flow: + column lays children in a single row left-to-right; overflow-x + auto provides the scroll. scroll-snap-type x mandatory makes + swipe snap to tile edges. Grid-only per CLAUDE.md (no flexbox). */ + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(160px, 200px); + gap: var(--space-2, 8px); + overflow-x: auto; + overflow-y: hidden; + padding: var(--space-1, 4px) 0 var(--space-2, 8px) 0; + scroll-snap-type: x mandatory; + scrollbar-width: thin; +} + +div.tag-lane-tile { + /* Tiles sit inside the horizontal scroll row. The default + .serp-item rules (hover, transition, border-radius) still + apply since we kept that class on the tile element. */ + scroll-snap-align: start; + /* Make the whole tile occupy its grid track. */ + width: 100%; + /* CRITICAL: grid items default to min-width: auto, which lets a + large img inside push the grid track past grid-auto-columns' + max (200px). Forcing min-width: 0 makes the track respect the + cap — without this, a 1500×1500 marketing thumbnail (e.g. + printableprompts "Color With Kindness") explodes its tile + across the full viewport. Same fix on min-width: 0 for the img + belt-and-suspenders so max-width: 100% can actually clamp. */ + min-width: 0; +} +div.tag-lane-tile img.serp-thumbnail { + /* Per CLAUDE.md CSS Media Sizing: width auto + max-width 100% + + max-height. Never combine width: 100% with max-height — + portrait thumbs leave a little whitespace, which is acceptable. + min-width: 0 lets max-width: 100% actually clamp (see the tile + rule above for the grid-item min-content gotcha). */ + width: auto; + max-width: 100%; + max-height: 200px; + min-width: 0; +} + +@media (max-width: 800px) { + /* Mobile: narrower tiles + breathing room on the swipe edge */ + div.tag-lane-grid { + grid-auto-columns: minmax(140px, 160px); + padding-right: var(--space-4, 16px); + } + div.tag-lane-tile img.serp-thumbnail { + max-height: 160px; + } +} + /* Tag editor bulk list */ ul.tag-list { list-style: none; diff --git a/make_post_sell/static/js/tag_bulk.js b/make_post_sell/static/js/tag_bulk.js new file mode 100644 index 0000000..74976ff --- /dev/null +++ b/make_post_sell/static/js/tag_bulk.js @@ -0,0 +1,274 @@ +/* MPS-24 Phase 2.4: Single-page progressive enhancement for /s/{id}/tags. + * + * Capability-driven per CLAUDE.md: every form on the bulk tagger is a + * real