fix: MPS-24 Phase 2.8 — bulk tagger AJAX tag-focus + real drag-to-reorder
Operator (printableprompts.com, 481 products) reported the bulk tagger
'still refreshing the whole screen' and 'dragging tags doesn't work'
after 2.7. Two real defects the 2.7 static audit missed:
1. Tag-focus was a full-page navigation: clicking a tag chip is
<a href=?focus=slug>, and the view loaded+rendered ALL products on
EVERY GET. On a 481-product catalog every tag click reloaded a
multi-MB page. The forms were AJAX; the dominant workflow was not.
2. Drag-to-reorder never existed: shop_tags.j2 shipped draggable=true +
a handle + help text, but tag_bulk.js had ZERO drag handlers.
Fix:
- shop.py:shop_tags — all_products loads only when focus_tag or
show_suggestions (bare GET is light). New AJAX branch: is_ajax +
?focus=slug -> JSON {focus, products:[{id,title,url,attached}]}.
- shop_tags.j2 — stable [data-focus-section] (always in DOM, hidden
until focused); ?focus= chips carry data-tag-focus-link. No-JS
unchanged (real navigation, server renders the section).
- tag_bulk.js — wireFocusLinks() intercepts chip clicks, fetchFocus()
+ renderFocus() swap the list in place, active-chip + history
pushState/popstate, real-navigation fallback. wireDragAndDrop()
HTML5 DnD -> persistOrder() POSTs action=set_order&tag_slugs=…
(view already supported it) + re-syncs up/down disabled states.
.tag-list-dragging CSS added.
- Tests: TestProductTagsSpa +4 (ajax focus json, unknown-slug null,
set_order persists positions, bare GET no catalog). 1128 passed.
Docs: mps-24.md Phase 2.8, architecture.md, design-system.md, CLAUDE.md.
Deferred: AJAX 'Suggest categories' link (occasional click, not hot path).
This commit is contained in:
parent
f591620424
commit
155f7ff66f
9 changed files with 439 additions and 10 deletions
|
|
@ -222,6 +222,7 @@ mps_page_session (raw rows)
|
|||
| Facet nav on shop home (MPS-24 Phase 2.6b) | `_facet_nav.j2` macros + `home.j2` / `shop.j2` wrappers | Desktop sidebar + mobile `<details>` accordion when `shop.home_layout >= 1` | Opt-in via home_layout |
|
||||
| Mobile SERP rows under each lane (MPS-24 Phase 2.6b) | `home.j2` / `shop.j2` `.tag-lane-rows` markup + CSS visibility swap | Horizontal tiles ≥800px; vertical SERP rows with 6-sentence excerpts <800px | On for layout 2 |
|
||||
| Per-product SPA tag chips (MPS-24 Phase 2.7) | `product_tags` view (`/p/{id}/tags`) + `static/js/product_tags.js` + `views/__init__.py:is_ajax()` | Chip add/remove on product edit, AJAX persist, no full page reload; no-JS keeps the comma `tags` field on the main form | Always on (JS-enhanced; no-JS fallback) |
|
||||
| Bulk tagger AJAX focus + DnD (MPS-24 Phase 2.8) | `shop.py:shop_tags` AJAX `?focus=` branch + `tag_bulk.js` `wireFocusLinks`/`wireDragAndDrop` | Tag chip click swaps product list in place (no reload); HTML5 drag-to-reorder POSTs `set_order`; bare GET no longer loads the whole catalog | Always on (JS-enhanced; no-JS = real navigation) |
|
||||
|
||||
## Ticket Index
|
||||
|
||||
|
|
@ -251,7 +252,7 @@ mps_page_session (raw rows)
|
|||
| [MPS-21](tickets/mps-21.md) | Make-an-Offer Mode | Complete |
|
||||
| [MPS-22](tickets/mps-22.md) | Kill-Switch Feature Flags — Karaoke + Torrent Off by Default | Complete |
|
||||
| [MPS-23](tickets/mps-23.md) | Consolidated Transactional Sender Identity + Shop Contact Email | Open |
|
||||
| [MPS-24](tickets/mps-24.md) | Shop home page overhaul + product categorization (tags + chips + lanes + auto-suggest + per-product SPA tag chips) | In progress (Phases 1 + 2 + 2.6 + 2.7 landed) |
|
||||
| [MPS-24](tickets/mps-24.md) | Shop home page overhaul + product categorization (tags + chips + lanes + auto-suggest + per-product SPA tag chips + bulk-tagger AJAX/DnD) | In progress (Phases 1 + 2 + 2.6 + 2.7 + 2.8 landed) |
|
||||
|
||||
## Related Docs
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,9 @@ All components are documented with live examples at `/styleguide`. The styleguid
|
|||
| `.tag-chip-add` | `product_edit.j2` | Add-a-tag row, grid `1fr auto`, stacks to one column under 600px |
|
||||
| `.tag-chip-flash` | `product_edit.j2` | Toast region reusing `.tag-flash-toast` / `.tag-flash-{success,error,info}` |
|
||||
| `[data-product-tags]` / `[data-product-tags-url]` | `product_edit.j2` | JS hooks for `product_tags.js`: container + the `/p/{id}/tags` endpoint URL |
|
||||
| `[data-focus-section]` / `[data-focus-heading]` / `[data-focus-list]` | `shop_tags.j2` (Phase 2.8) | Stable bulk-tagger focus container — always in the DOM, `hidden` until a tag is focused; `tag_bulk.js` swaps the product list in place instead of a full reload |
|
||||
| `[data-tag-focus-link]` | `shop_tags.j2` (Phase 2.8) | Tag chip in the All-tags list; `tag_bulk.js` intercepts the click and fetches `?focus=<slug>` as JSON |
|
||||
| `.tag-list-dragging` / `.tag-list-drop-target` | `shop_tags.j2` (Phase 2.8) | Dragged row (dimmed) + active drop position during HTML5 drag-to-reorder; `tag_bulk.js` POSTs `set_order` on drop |
|
||||
|
||||
## CSS Conventions
|
||||
|
||||
|
|
|
|||
|
|
@ -319,6 +319,50 @@ 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.8 — bulk tagger: AJAX tag-focus + real drag-to-reorder + page-weight fix (shipped 2026-05-16)
|
||||
|
||||
Operator (printableprompts.com, **481 products**) reported the bulk
|
||||
tagger "still refreshing the whole screen" and "dragging tags doesn't
|
||||
work" after 2.7. Two real defects the 2.7 static audit missed:
|
||||
|
||||
1. **Tag-focus was a full-page navigation.** Clicking a tag chip is
|
||||
`<a href="?focus=slug">` — a real reload. On a 481-product catalog
|
||||
the view loaded + rendered *every* product on *every* GET, so each
|
||||
tag click reloaded a multi-MB page. The forms were AJAX; the
|
||||
dominant workflow action (focus a tag → assign products) was not.
|
||||
2. **Drag-to-reorder never existed.** `shop_tags.j2` shipped
|
||||
`draggable="true"`, a ≡ handle, and "drag rows when JS is enabled"
|
||||
help text, but `tag_bulk.js` had **zero** drag handlers — only the
|
||||
↑/↓ buttons worked. The affordance lied.
|
||||
|
||||
Fix:
|
||||
|
||||
- **View** (`shop.py:shop_tags`): `all_products` now loads only when
|
||||
`focus_tag or show_suggestions` (bare GET is light). New AJAX branch:
|
||||
`is_ajax + ?focus=<slug>` → JSON `{focus:{name,slug},
|
||||
products:[{id,title,url,attached}]}`.
|
||||
- **Template** (`shop_tags.j2`): focus section is now a stable
|
||||
`[data-focus-section]` (always in DOM, `hidden` until focused);
|
||||
`?focus=` chips carry `data-tag-focus-link` + `data-tag-slug`.
|
||||
No-JS unchanged: the link is a real navigation, server still renders
|
||||
the section.
|
||||
- **JS** (`tag_bulk.js`): `wireFocusLinks()` intercepts chip clicks →
|
||||
`fetchFocus()` → `renderFocus()` swaps the product list in place,
|
||||
updates the active chip, `history.pushState` (back/forward via
|
||||
`popstate`), graceful real-navigation fallback. `wireDragAndDrop()`
|
||||
implements HTML5 DnD on the tag rows → `persistOrder()` POSTs
|
||||
`action=set_order&tag_slugs=…` (view already supported it) and
|
||||
re-syncs ↑/↓ disabled states. `.tag-list-dragging` CSS added.
|
||||
|
||||
Tests (`test_functional.py::TestProductTagsSpa`):
|
||||
`test_ajax_focus_returns_product_list_json`,
|
||||
`test_ajax_focus_unknown_slug_returns_null_focus`,
|
||||
`test_ajax_set_order_persists_tag_positions`,
|
||||
`test_bulk_tagger_bare_get_renders_without_products`.
|
||||
|
||||
Deferred (occasional click, not the hot path): AJAX-ifying the
|
||||
"Suggest categories" link — still a full navigation by design.
|
||||
|
||||
### Phase 2.7 — per-product SPA tag chips on product edit (shipped 2026-05-16)
|
||||
|
||||
Operator report: adding/removing a tag on the product edit page
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue