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.