diff --git a/docs/tickets/mps-24.md b/docs/tickets/mps-24.md
index d774136..063d1f7 100644
--- a/docs/tickets/mps-24.md
+++ b/docs/tickets/mps-24.md
@@ -363,6 +363,19 @@ Tests (`test_functional.py::TestProductTagsSpa`):
Deferred (occasional click, not the hot path): AJAX-ifying the
"Suggest categories" link — still a full navigation by design.
+**Phase 2.8i — chip strip on every SERP page** (shipped 2026-05-16):
+operator: "leave the chits on screen for all serp pages." The
+horizontal `tag-chip-strip` rendered only on the shop home; drilling
+into a category (tag-detail SERP `shop_tag.j2`) dropped it, so hopping
+categories meant going back. Extracted the (duplicated) chip strip
+from `home.j2`/`shop.j2` into a single `_facet_nav.j2` `chip_strip(...)`
+macro and added it to `shop_tag.j2` under the header. The
+`shop_tag_detail` view already supplied `home_chips` / `active_tag` /
+sort / price, so this was a template-only gap; the active category
+chip highlights on the SERP and carries `facet_qs`. Search SERP
+already renders `home.j2` so it gets the macro for free. Test:
+`test_chip_strip_stays_on_tag_detail_serp`.
+
**Phase 2.8h — facets compose, not clobber** (shipped 2026-05-16):
operator: "switching one breaks it" — picking a category reset the
active Sort + Price. Cause: the facet category links / "All" link /
diff --git a/make_post_sell/templates/_facet_nav.j2 b/make_post_sell/templates/_facet_nav.j2
index 004eab8..98102a1 100644
--- a/make_post_sell/templates/_facet_nav.j2
+++ b/make_post_sell/templates/_facet_nav.j2
@@ -118,3 +118,22 @@
{{ facet_form(base_url, tag_base, sort_options, sort_key, price_min, price_max, facet_tags, active_tag) }}
{% endmacro %}
+
+{# Horizontal category chip strip. Single source of truth — used by the
+ shop home (home.j2 / shop.j2) AND every SERP page (shop_tag.j2) so
+ the chips stay on screen as the shopper hops categories. Carries the
+ active sort/price (facet_qs) so switching category composes. #}
+{% macro chip_strip(shop_url, tag_base, home_chips, active_tag, sort_key, price_min, price_max) %}
+{% if home_chips %}
+
+{% endif %}
+{% endmacro %}
diff --git a/make_post_sell/templates/home.j2 b/make_post_sell/templates/home.j2
index 0422b72..b0e5766 100644
--- a/make_post_sell/templates/home.j2
+++ b/make_post_sell/templates/home.j2
@@ -88,24 +88,11 @@
Layout 0 (flat) keeps its existing simple grid with no nav. #}
{% set show_facet_nav = request.shop.home_layout and request.shop.home_layout >= 1 %}
- {# MPS-24: tag chip strip — shown on layout 1 (chips) and 2 (lanes).
- Chips and the facet left-nav point at the SAME targets (shop home
- for "All", {tag_base}/tag/{slug} per category) so the two stay in
- lock-step. tag_filter.js keeps full navigation for /tag/ hrefs. #}
- {% if home_chips %}
-
- {% endif %}
+ {# MPS-24: tag chip strip — shared macro, shown on layout 1 (chips)
+ and 2 (lanes) AND every SERP page (shop_tag.j2). Chips + facet
+ left-nav hit the SAME targets; tag_filter.js keeps full navigation
+ for /tag/ hrefs. #}
+ {{ facet.chip_strip(shop_url, tag_base, home_chips, active_tag, sort_key, price_min, price_max) }}
{% if show_facet_nav %}
{# Mobile/tablet: collapsed facets above the grid. #}
diff --git a/make_post_sell/templates/shop.j2 b/make_post_sell/templates/shop.j2
index db01a8b..d19828b 100644
--- a/make_post_sell/templates/shop.j2
+++ b/make_post_sell/templates/shop.j2
@@ -15,27 +15,12 @@
{% set tag_base = request.shop.absolute_url(request, slug=False) %}
{% set show_facet_nav = request.shop.home_layout and request.shop.home_layout >= 1 %}
-{# MPS-24: tag chip strip — shown on layout 1 (chips) and 2 (lanes). #}
-{# Chip strip and the facet left-nav point at the SAME targets: the
- shop home for "All" and the canonical tag detail page
- ({tag_base}/tag/{slug}) per category. tag_filter.js detects the
- /tag/ href and keeps full navigation, so a chip and its matching
- sidebar entry always land on the same page with the same active
- state. #}
-{% if home_chips %}
-
-{% endif %}
+{# MPS-24: tag chip strip — shared macro (single source of truth),
+ shown on layout 1 (chips) and 2 (lanes) AND every SERP page. Chip
+ strip + facet left-nav point at the SAME targets; tag_filter.js
+ keeps full navigation for /tag/ hrefs so a chip and its sidebar
+ entry land on the same SERP with the same active state. #}
+{{ facet.chip_strip(shop_url, tag_base, home_chips, active_tag, sort_key, price_min, price_max) }}
{% if show_facet_nav %}
{# Mobile/tablet: collapsed facets above the grid. #}
diff --git a/make_post_sell/templates/shop_tag.j2 b/make_post_sell/templates/shop_tag.j2
index 6c0562a..f1783a1 100644
--- a/make_post_sell/templates/shop_tag.j2
+++ b/make_post_sell/templates/shop_tag.j2
@@ -11,6 +11,11 @@
+{# Chip strip stays on screen on every SERP page so the shopper can
+ hop categories without going back. Same shared macro as the shop
+ home; active_tag highlights the current category. #}
+{{ facet.chip_strip(shop_url, tag_base, home_chips, active_tag, sort_key, price_min, price_max) }}
+
{# Mobile/tablet: collapsed facets above the grid (CSS hides
it ≥800px where the sidebar takes over). #}
{{ facet.details(shop_url, tag_base, sort_options, sort_key, price_min, price_max, facet_tags, active_tag) }}
diff --git a/make_post_sell/tests/test_functional.py b/make_post_sell/tests/test_functional.py
index 1626c62..b257c54 100644
--- a/make_post_sell/tests/test_functional.py
+++ b/make_post_sell/tests/test_functional.py
@@ -8947,6 +8947,43 @@ class TestHomeLayoutAndTags(_AuthenticatedBase):
self.assertIn('tag-detail-header', body)
self.assertIn('serp-list', body)
+ def test_chip_strip_stays_on_tag_detail_serp(self):
+ """Operator: 'leave the chits on screen for all serp pages.'
+ The horizontal chip strip must render on the tag-detail SERP
+ (not just the shop home) so the shopper can hop categories
+ without going back. The current category chip is active."""
+ shop, product = self._make_shop_with_product("chip-serp-shop")
+ self.testapp.post(
+ f"/s/{shop.id}/settings",
+ {
+ "form_section": "home-layout-settings",
+ "home_layout": "2",
+ "home_layout_tag_limit": "5",
+ "home_layout_per_lane_limit": "10",
+ "submit": "Save Home Layout",
+ },
+ )
+ self.testapp.post(
+ f"/p/{product.id}/edit",
+ {
+ "title": product.title,
+ "description": "One. Two. Three. Four. Five. Six.",
+ "price": str(product.price),
+ "visibility": "1",
+ "tags": "Math",
+ },
+ )
+ res = self.testapp.get(f"/s/{shop.id}/tag/math")
+ body = res.body.decode()
+ # The shared chip strip is present on the SERP page.
+ self.assertIn('class="tag-chip-strip"', body)
+ self.assertIn('data-tag-strip', body)
+ # The active category chip is highlighted on the SERP.
+ self.assertIn('tag-chip tag-chip-active', body)
+ # And it still has the facet sidebar (chip strip is additive,
+ # not a replacement).
+ self.assertIn('facet-nav', body)
+
def test_facet_links_preserve_sort_and_price(self):
"""Operator report: 'switching one breaks it' — changing category
reset sort/price. Every category link / All link / chip / lane