From f7b792923defc8466c045a6928793a1ed729fe00 Mon Sep 17 00:00:00 2001 From: Julia Hoang Date: Tue, 30 Jun 2026 14:13:39 -0700 Subject: [PATCH 1/8] feat: add libraries highlight carousel card --- ak/homepage.py | 75 +++++-- ak/views.py | 6 +- ..._homepagesettings_highlighted_libraries.py | 24 +++ core/models.py | 29 ++- core/views.py | 41 ++++ libraries/models.py | 4 + static/css/v3/components.css | 1 + static/css/v3/library-highlight-carousel.css | 199 ++++++++++++++++++ static/css/v3/v3-homepage.css | 10 +- .../v3/examples/_v3_example_section.html | 9 + templates/v3/homepage.html | 3 +- .../includes/_library_highlight_carousel.html | 144 +++++++++++++ 12 files changed, 514 insertions(+), 31 deletions(-) create mode 100644 core/migrations/0009_homepagesettings_highlighted_libraries.py create mode 100644 static/css/v3/library-highlight-carousel.css create mode 100644 templates/v3/includes/_library_highlight_carousel.html diff --git a/ak/homepage.py b/ak/homepage.py index f7be04b2d..f2e9be407 100644 --- a/ak/homepage.py +++ b/ak/homepage.py @@ -5,19 +5,11 @@ from core.constants import SLACK_MEMBER_COUNT from core.models import HomepageSettings from core.templatetags.custom_static import large_static -from libraries.models import LibraryVersion, Tier -from libraries.utils import build_library_intro_context +from libraries.models import FEATURED_LIBRARY_TIERS, Library, LibraryVersion +from libraries.utils import build_library_intro_context, get_documentation_url from news.models import Entry from versions.models import Version -# Fallback code snippet shown on the Get Started card when no featured -# library snippet is available. TODO: replace with a proper empty state. -HELLO_WORLD_SNIPPET = """#include -int main() -{ - std::cout << "Hello, Boost."; -}""" - # Hero illustration for the V3 homepage. HERO_LEGACY_IMAGE_URL_LIGHT = large_static("img/v3/home-page/heros.png") HERO_LEGACY_IMAGE_URL_DARK = large_static("img/v3/home-page/heros_light.png") @@ -45,7 +37,7 @@ def get_v3_featured_library(): return ( LibraryVersion.objects.filter( version=latest_version, - library__tier__in=[Tier.FLAGSHIP, Tier.CORE], + library__tier__in=FEATURED_LIBRARY_TIERS, ) .order_by("?") .first() @@ -152,17 +144,62 @@ def build_join_developers_links(): ] -def build_get_started_code(): - """The Get Started code card. Hardcoded for now; a redesign of this - component is tracked in a separate PR. +def build_library_highlight_carousel(limit=3): + """Per-slide content for the homepage 'Explore battle tested libraries' + carousel. + + Shows the libraries an editor curated in HomepageSettings first (in random + order); when fewer than `limit` are curated, the remaining slots are filled + with random flagship/core libraries in the latest release. Returns a list + of dicts (name, category_tags, description, added_in_version, docs_url); + empty when no candidate library exists. """ - return { - "heading": "Get started with our libraries", - "code": HELLO_WORLD_SNIPPET, - "language": "cpp", - "library_slug": "", + latest = Version.objects.most_recent() + libraries = list( + HomepageSettings.load() + .highlighted_libraries.prefetch_related("categories") + .order_by("?") + ) + + remaining_slots = limit - len(libraries) + if remaining_slots > 0: + libraries += list( + Library.objects.filter( + tier__in=FEATURED_LIBRARY_TIERS, + library_version__version=latest, + ) + .exclude(id__in=[lib.id for lib in libraries]) + .distinct() + .prefetch_related("categories") + .order_by("?")[:remaining_slots] + ) + if not libraries: + return [] + + library_versions = { + lv.library_id: lv + for lv in LibraryVersion.objects.filter(library__in=libraries, version=latest) } + slides = [] + for library in libraries: + lv = library_versions.get(library.id) + first_version = library.first_boost_version + slides.append( + { + "name": library.display_name_short, + "category_tags": library.category_tags, + "description": ( + (lv.description if lv else None) or library.description or "" + ), + "added_in_version": ( + first_version.display_name if first_version else "" + ), + "docs_url": get_documentation_url(lv, latest=True) if lv else "", + } + ) + return slides + def build_library_intro(): """Library Intro card content for the V3 homepage, or None if no diff --git a/ak/views.py b/ak/views.py index e4425e3a4..45f76f830 100644 --- a/ak/views.py +++ b/ak/views.py @@ -26,8 +26,8 @@ from ak.homepage import ( WHY_BOOST_CARDS, build_community_posts, - build_get_started_code, build_join_developers_links, + build_library_highlight_carousel, build_library_intro, hero_image_context, ) @@ -116,8 +116,8 @@ def get_v3_context_data(self, **kwargs): # Testimonial Card ctx["testimonial_cards"] = get_testimonial_cards(limit=5) - # Get Started Card - ctx["get_started_code"] = build_get_started_code() + # Library Highlight Carousel ("Explore battle tested libraries") + ctx["library_highlight_carousel"] = build_library_highlight_carousel() # Library Intro Card ctx["library_intro"] = build_library_intro() diff --git a/core/migrations/0009_homepagesettings_highlighted_libraries.py b/core/migrations/0009_homepagesettings_highlighted_libraries.py new file mode 100644 index 000000000..dcca74e31 --- /dev/null +++ b/core/migrations/0009_homepagesettings_highlighted_libraries.py @@ -0,0 +1,24 @@ +# Generated by Django 6.0.2 on 2026-06-30 16:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0008_popularsearchterm_popularsearchtermexclusion"), + ("libraries", "0041_category_short_description"), + ] + + operations = [ + migrations.AddField( + model_name="homepagesettings", + name="highlighted_libraries", + field=models.ManyToManyField( + blank=True, + help_text="Libraries shown in the homepage 'Explore battle tested libraries' carousel. Only flagship and core libraries present in the latest stable release are listed. If empty, a random selection of flagship or core libraries is used.", + related_name="+", + to="libraries.library", + ), + ), + ] diff --git a/core/models.py b/core/models.py index 8bd76c53a..86df0eebe 100644 --- a/core/models.py +++ b/core/models.py @@ -144,18 +144,20 @@ def wordcloud_ignore_set(self): class HomepageSettingsForm(WagtailAdminModelForm): - """Scopes the featured-library chooser to flagship and core libraries - present in the latest stable release, A→Z.""" + """Scopes the library choosers to flagship and core libraries present in + the latest stable release, A→Z.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - from libraries.models import Library, Tier + from libraries.models import FEATURED_LIBRARY_TIERS, Library - qs = Library.objects.filter(tier__in=[Tier.FLAGSHIP, Tier.CORE]) + qs = Library.objects.filter(tier__in=FEATURED_LIBRARY_TIERS) latest = Version.objects.most_recent() if latest: qs = qs.filter(library_version__version=latest) - self.fields["featured_library"].queryset = qs.order_by(Lower("name")) + qs = qs.order_by(Lower("name")) + self.fields["featured_library"].queryset = qs + self.fields["highlighted_libraries"].queryset = qs @register_setting @@ -178,7 +180,22 @@ class HomepageSettings(BaseGenericSetting): ), ) - panels = [FieldPanel("featured_library")] + highlighted_libraries = models.ManyToManyField( + "libraries.Library", + blank=True, + related_name="+", + help_text=( + "Libraries shown in the homepage 'Explore battle tested " + "libraries' carousel. Only flagship and core libraries present in " + "the latest stable release are listed. If empty, a random " + "selection of flagship or core libraries is used." + ), + ) + + panels = [ + FieldPanel("featured_library"), + FieldPanel("highlighted_libraries"), + ] class Meta: verbose_name = "Homepage Settings" diff --git a/core/views.py b/core/views.py index 0729fd92b..04d4cda10 100644 --- a/core/views.py +++ b/core/views.py @@ -1852,6 +1852,47 @@ def get_context_data(self, **kwargs): }, ] + context["demo_library_highlight_slides"] = [ + { + "name": "Beast", + "category_tags": [ + {"label": "Concurrent", "slug": "concurrent"}, + {"label": "IO", "slug": "io"}, + ], + "description": ( + "Portable HTTP, WebSocket, and network operations using only " + "C++11 and Boost.Asio. Designed for performance at scale." + ), + "added_in_version": "1.66.0", + "docs_url": "#", + }, + { + "name": "Asio", + "category_tags": [ + {"label": "Concurrent", "slug": "concurrent"}, + {"label": "IO", "slug": "io"}, + ], + "description": ( + "Cross-platform C++ library for network and low-level I/O " + "programming with a consistent asynchronous model." + ), + "added_in_version": "1.35.0", + "docs_url": "#", + }, + { + "name": "Hana", + "category_tags": [ + {"label": "Metaprogramming", "slug": "metaprogramming"}, + ], + "description": ( + "A modern C++ metaprogramming library for computations on both " + "types and values." + ), + "added_in_version": "1.61.0", + "docs_url": "#", + }, + ] + context["learn_card_data"] = { "title": "I want to learn:", "text": "How to install Boost, use its libraries, build projects, and get help when you need it.", diff --git a/libraries/models.py b/libraries/models.py index f6da98e97..d36267d4a 100644 --- a/libraries/models.py +++ b/libraries/models.py @@ -223,6 +223,10 @@ class Tier(models.IntegerChoices): LEGACY = 40, "Legacy" +# Tiers eligible to be featured on the V3 homepage (spotlight + highlight carousel). +FEATURED_LIBRARY_TIERS = [Tier.FLAGSHIP, Tier.CORE] + + class Library(models.Model): """ Model to represent component Libraries of Boost diff --git a/static/css/v3/components.css b/static/css/v3/components.css index 290733966..52e77bbb6 100644 --- a/static/css/v3/components.css +++ b/static/css/v3/components.css @@ -5,6 +5,7 @@ @import "./avatar.css"; @import "./carousel-buttons.css"; @import "./cards-carousel.css"; +@import "./library-highlight-carousel.css"; @import "./badge.css"; @import "./count-badge.css"; @import "./v3-examples-section.css"; diff --git a/static/css/v3/library-highlight-carousel.css b/static/css/v3/library-highlight-carousel.css new file mode 100644 index 000000000..6aa54435e --- /dev/null +++ b/static/css/v3/library-highlight-carousel.css @@ -0,0 +1,199 @@ +/* + Library Highlight carousel — "Explore battle tested libraries". + CSS-only, radio-driven (no JavaScript): hidden radios translate the track + and swap in the active slide's nav block; prev/next wrap cyclically. + Dependencies: carousel-buttons.css, category-tags.css, buttons.css +*/ + +.library-highlight-carousel { + display: flex; + flex-direction: column; + gap: var(--space-medium); + padding: var(--space-large) 0; + width: 100%; + max-width: 458px; + min-width: 0; + box-sizing: border-box; + background: var(--color-surface-weak); + border: 1px solid var(--color-stroke-weak); + border-radius: var(--border-radius-xl); + overflow: hidden; +} + +.library-highlight-carousel__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--space-large); + gap: var(--space-medium); + width: 100%; +} + +.library-highlight-carousel__heading { + margin: 0; + font-family: var(--font-display); + font-size: var(--font-size-large); + font-weight: var(--font-weight-medium); + line-height: 1.1; + letter-spacing: -0.24px; + color: var(--color-text-primary); + min-width: 0; +} + +/* Hidden radios are the single source of carousel state. */ +.library-highlight-carousel__radio { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* One nav block per slide; only the active slide's block is shown via the + per-state rules emitted in the template. */ +.library-highlight-carousel__nav { + display: none; +} + +/* ── Translate track: one slide per view ───────── */ + +.library-highlight-carousel__track { + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} + +.library-highlight-carousel__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + gap: 0; + align-items: stretch; + height: 100%; + min-width: 0; + transition: transform 0.3s ease; + will-change: transform; +} + +.library-highlight-carousel__item { + list-style: none; + flex: 0 0 100%; + min-width: 100%; + display: flex; + flex-direction: column; +} + +/* ── Yellow library card ───────────────────────── */ + +.library-highlight-carousel__card { + display: flex; + flex-direction: column; + gap: var(--space-medium); + padding: var(--space-large); + margin: 0 var(--space-large); + background: var(--color-surface-weak-accent-yellow); + border: 1px solid var(--color-accent-strong-yellow); + border-radius: var(--border-radius-l); +} + +.library-highlight-carousel__card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-medium); +} + +.library-highlight-carousel__name { + margin: 0; + font-family: var(--font-display); + font-size: var(--font-size-large); + font-weight: var(--font-weight-medium); + line-height: 1.1; + color: var(--color-text-primary); + min-width: 0; +} + +.library-highlight-carousel__tags { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: var(--space-xs); + flex-shrink: 0; +} + +.library-highlight-carousel__description { + margin: 0; + padding: 0; + color: var(--color-text-secondary); + font-size: var(--font-size-small); + line-height: var(--line-height-relaxed); + display: -webkit-box; + line-clamp: 3; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + /* Always reserve 3 lines so every card resolves to the same fit-content + height regardless of how long the description actually is. */ + height: calc(3 * var(--line-height-relaxed) * var(--font-size-small)); +} + +/* ── Static meta row: "Added in" + dots ────────── */ +/* Stays put while the card translates; per-slide content swaps via the + inline :checked rules emitted in the template. */ + +.library-highlight-carousel__meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-medium); + padding: 0 var(--space-large); +} + +.library-highlight-carousel__added { + display: none; + color: var(--color-text-secondary); + font-size: var(--font-size-small); +} + +.library-highlight-carousel__dots { + list-style: none; + margin: 0 0 0 auto; + padding: 0; + display: flex; + align-items: center; + gap: var(--space-xs); +} + +.library-highlight-carousel__dot { + width: 12px; + height: 12px; + border-radius: var(--border-radius-s); + background: var(--color-surface-strong); + cursor: pointer; +} + +.library-highlight-carousel__divider { + margin: 0; + border: none; + border-top: 1px solid var(--color-stroke-weak); + width: 100%; + height: 0; +} + +/* One CTA per slide stacked in place; only the active one is shown. */ +.library-highlight-carousel__button { + display: none; + margin: 0 var(--space-large); +} + +.library-highlight-carousel__empty { + margin: 0; + color: var(--color-text-secondary); +} diff --git a/static/css/v3/v3-homepage.css b/static/css/v3/v3-homepage.css index 55bdd38c1..425129385 100644 --- a/static/css/v3/v3-homepage.css +++ b/static/css/v3/v3-homepage.css @@ -132,8 +132,12 @@ body:has(.homepage-v3) .header { max-width: 100%; } + .card-masonry-top .item-d .library-highlight-carousel { + max-width: 100%; + } + .card-masonry-top { - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-areas: "a f" "a c" @@ -145,6 +149,10 @@ body:has(.homepage-v3) .header { ; } + .card-masonry-top > div { + min-width: 0; + } + .card-masonry-top .item-f { display: contents; grid-area: f; diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index 06d00bb9a..5cb627b1e 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -280,6 +280,15 @@

{{ section_title }}

{% endwith %} + {% with section_title="Library Highlight Carousel" %} +
+

{{ section_title }}

+
+ {% include "v3/includes/_library_highlight_carousel.html" with carousel_id="library-highlight-demo" slides=demo_library_highlight_slides %} +
+
+ {% endwith %} + {% with section_title="Detail card carousel with autoplay" %}

{{ section_title }}

diff --git a/templates/v3/homepage.html b/templates/v3/homepage.html index 145733f0b..c3ca6705f 100644 --- a/templates/v3/homepage.html +++ b/templates/v3/homepage.html @@ -31,8 +31,7 @@ {% include "v3/includes/_post_card.html" with heading="Posts from the Boost Community" items=community_posts variant="card" theme="teal" primary_cta_label="View all posts" primary_cta_url=posts_url %}
- {% url 'library-detail' version_slug='latest' library_slug=get_started_code.library_slug as get_started_url %} - {% include "v3/includes/_code_block_card.html" with heading=get_started_code.heading code=get_started_code.code language=get_started_code.language block_variant="grey-bg" button_text="View library" button_url=get_started_url button_aria_label="Explore examples" %} + {% include "v3/includes/_library_highlight_carousel.html" with carousel_id="library-highlight" slides=library_highlight_carousel %}
{% include "v3/includes/_join_card.html" with heading="Join developers building the future of C++" items=join_developers_links variant="card" theme="yellow" primary_cta_label="Explore the community" primary_cta_url=community_url %} diff --git a/templates/v3/includes/_library_highlight_carousel.html b/templates/v3/includes/_library_highlight_carousel.html new file mode 100644 index 000000000..e58d06ed9 --- /dev/null +++ b/templates/v3/includes/_library_highlight_carousel.html @@ -0,0 +1,144 @@ +{% comment %} + Library Highlight carousel — "Explore battle tested libraries". + CSS-only, radio-driven carousel (no JavaScript), mirroring the testimonial + card: hidden radios are the state. Only the yellow card translates; the meta + row (added-in + dots), divider and CTA stay static and swap their per-slide + content via the inline + + {# Hidden radios drive all carousel state via the per-state rules above. #} + {% for slide in slides %} + + {% endfor %} + {% endif %} + + + + {# Translating track — only the yellow cards move. #} + + + {# Static meta row — content swaps per active slide, position never moves. #} + + + + + {# Static CTA — one per slide stacked in place, only the active one shown. #} + {% for slide in slides %} + {% with cnt=forloop.counter|stringformat:"s" %} + {% include "v3/includes/_button.html" with label="View documentation" url=slide.docs_url|default:"#" extra_classes="btn-flex library-highlight-carousel__button library-highlight-carousel__button--"|add:cnt %} + {% endwith %} + {% endfor %} + {% else %} + + {# Empty state: no libraries configured and no fallback available. #} + + {% endif %} + +{% endwith %} From 79bd8134b076e2fd4c2b5bcbb89b0bc8dca3d9cd Mon Sep 17 00:00:00 2001 From: Julia Hoang Date: Tue, 30 Jun 2026 15:51:58 -0700 Subject: [PATCH 2/8] style: apply dark mode styling override --- static/css/v3/library-highlight-carousel.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/static/css/v3/library-highlight-carousel.css b/static/css/v3/library-highlight-carousel.css index 6aa54435e..28078cad5 100644 --- a/static/css/v3/library-highlight-carousel.css +++ b/static/css/v3/library-highlight-carousel.css @@ -197,3 +197,15 @@ margin: 0; color: var(--color-text-secondary); } + +/* Dark mode overrides */ +html.dark { + .library-highlight-carousel__card { + border: none; + } + + .library-highlight-carousel__tags .category-tag { + background: var(--color-primary-grey-100); + color: var(--color-primary-grey-800); + } +} From 2455ad0e6a2cf01728f83f8cf002a505593aaf88 Mon Sep 17 00:00:00 2001 From: Julia Hoang Date: Fri, 3 Jul 2026 09:21:39 -0700 Subject: [PATCH 3/8] style: improve library carousel card height --- ak/homepage.py | 4 ++++ static/css/v3/library-highlight-carousel.css | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ak/homepage.py b/ak/homepage.py index f2e9be407..2af4b80b2 100644 --- a/ak/homepage.py +++ b/ak/homepage.py @@ -198,6 +198,10 @@ def build_library_highlight_carousel(limit=3): "docs_url": get_documentation_url(lv, latest=True) if lv else "", } ) + slides.sort( + key=lambda slide: (len(slide["description"]), len(slide["category_tags"])), + reverse=True, + ) # sort by description length, then category count, to improve UI appearance return slides diff --git a/static/css/v3/library-highlight-carousel.css b/static/css/v3/library-highlight-carousel.css index 28078cad5..946f15e4f 100644 --- a/static/css/v3/library-highlight-carousel.css +++ b/static/css/v3/library-highlight-carousel.css @@ -105,6 +105,7 @@ .library-highlight-carousel__card-head { display: flex; + flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--space-medium); @@ -139,9 +140,6 @@ -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; - /* Always reserve 3 lines so every card resolves to the same fit-content - height regardless of how long the description actually is. */ - height: calc(3 * var(--line-height-relaxed) * var(--font-size-small)); } /* ── Static meta row: "Added in" + dots ────────── */ From 8d51fdd0b810cea76707dc795b1eafbdfb744385 Mon Sep 17 00:00:00 2001 From: Julia Hoang Date: Thu, 9 Jul 2026 11:41:57 -0700 Subject: [PATCH 4/8] refactor: use union, count on queryset, build URL on BE (PR feedback) --- ak/homepage.py | 85 ++++++++++++------- .../includes/_library_highlight_carousel.html | 7 +- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/ak/homepage.py b/ak/homepage.py index 2af4b80b2..cf8a130b8 100644 --- a/ak/homepage.py +++ b/ak/homepage.py @@ -148,31 +148,37 @@ def build_library_highlight_carousel(limit=3): """Per-slide content for the homepage 'Explore battle tested libraries' carousel. - Shows the libraries an editor curated in HomepageSettings first (in random - order); when fewer than `limit` are curated, the remaining slots are filled - with random flagship/core libraries in the latest release. Returns a list - of dicts (name, category_tags, description, added_in_version, docs_url); - empty when no candidate library exists. + Shows the libraries an editor curated in HomepageSettings; when fewer than + `limit` are curated, the remaining slots are filled with random flagship/core + libraries in the latest release (admins may curate more than `limit`). When + admins curate libraries, their pinned order is preserved (fallback top-ups + follow); with no pins, slides are ordered by description length for UI + appearance. Returns a list of dicts (name, category_tags, description, + added_in_version, docs_url); empty when no candidate library exists. """ latest = Version.objects.most_recent() - libraries = list( - HomepageSettings.load() - .highlighted_libraries.prefetch_related("categories") - .order_by("?") + highlighted_ids = list( + HomepageSettings.load().highlighted_libraries.values_list("id", flat=True) ) + library_ids = list(highlighted_ids) - remaining_slots = limit - len(libraries) + # Admins may curate more than `limit`; only top up when they curate fewer. + remaining_slots = limit - len(highlighted_ids) if remaining_slots > 0: - libraries += list( + library_ids += list( Library.objects.filter( tier__in=FEATURED_LIBRARY_TIERS, library_version__version=latest, ) - .exclude(id__in=[lib.id for lib in libraries]) + .exclude(id__in=highlighted_ids) .distinct() - .prefetch_related("categories") - .order_by("?")[:remaining_slots] + .order_by("?") + .values_list("id", flat=True)[:remaining_slots] ) + + libraries = Library.objects.filter(id__in=library_ids).prefetch_related( + "categories" + ) if not libraries: return [] @@ -181,28 +187,45 @@ def build_library_highlight_carousel(limit=3): for lv in LibraryVersion.objects.filter(library__in=libraries, version=latest) } - slides = [] + category_list_url = reverse( + "libraries-list", + kwargs={"version_slug": "latest", "library_view_str": "list"}, + ) + + slides_by_id = {} for library in libraries: lv = library_versions.get(library.id) first_version = library.first_boost_version - slides.append( - { - "name": library.display_name_short, - "category_tags": library.category_tags, - "description": ( - (lv.description if lv else None) or library.description or "" - ), - "added_in_version": ( - first_version.display_name if first_version else "" - ), - "docs_url": get_documentation_url(lv, latest=True) if lv else "", - } - ) - slides.sort( + slides_by_id[library.id] = { + "name": library.display_name_short, + "category_tags": [ + ( + { + **tag, + "url": f"{category_list_url}?category={tag['slug']}", + "aria_label": f"Browse {tag['label']} libraries", + } + if tag["slug"] + else tag + ) + for tag in library.category_tags + ], + "description": ( + (lv.description if lv else None) or library.description or "" + ), + "added_in_version": (first_version.display_name if first_version else ""), + "docs_url": get_documentation_url(lv, latest=True) if lv else "", + } + + if highlighted_ids: + # Respect the admin's pinned order; fallback top-ups follow. + return [slides_by_id[lib_id] for lib_id in library_ids] + # No pins: order by description length, then category count, for looks. + return sorted( + slides_by_id.values(), key=lambda slide: (len(slide["description"]), len(slide["category_tags"])), reverse=True, - ) # sort by description length, then category count, to improve UI appearance - return slides + ) def build_library_intro(): diff --git a/templates/v3/includes/_library_highlight_carousel.html b/templates/v3/includes/_library_highlight_carousel.html index e58d06ed9..7824b9ee8 100644 --- a/templates/v3/includes/_library_highlight_carousel.html +++ b/templates/v3/includes/_library_highlight_carousel.html @@ -88,12 +88,7 @@ {% if slide.category_tags %} {% endif %} From 5eeccc6920dd3d0ab9ad4b04539b7d08918d8c53 Mon Sep 17 00:00:00 2001 From: Julia Hoang Date: Thu, 9 Jul 2026 14:16:16 -0700 Subject: [PATCH 5/8] refactor: improve first_boost_version query --- libraries/models.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/models.py b/libraries/models.py index d36267d4a..dc51da9bd 100644 --- a/libraries/models.py +++ b/libraries/models.py @@ -446,13 +446,12 @@ def github_properties(self): @cached_property def first_boost_version(self): """Returns the first Boost version that included this library""" - if not self.library_version.exists(): - return - return ( - self.library_version.order_by("version__release_date", "version__name") + first = ( + self.library_version.select_related("version") + .order_by("version__release_date", "version__name") .first() - .version ) + return first.version if first else None @cached_property def github_owner(self): From 4effb646ac4b183e0744dfd7e31acb2f1ae749c2 Mon Sep 17 00:00:00 2001 From: Julia Hoang Date: Thu, 9 Jul 2026 14:28:56 -0700 Subject: [PATCH 6/8] style: improve edge case styling --- static/css/v3/library-highlight-carousel.css | 4 ++-- templates/v3/includes/_library_highlight_carousel.html | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/static/css/v3/library-highlight-carousel.css b/static/css/v3/library-highlight-carousel.css index 946f15e4f..0dff56a31 100644 --- a/static/css/v3/library-highlight-carousel.css +++ b/static/css/v3/library-highlight-carousel.css @@ -124,9 +124,9 @@ .library-highlight-carousel__tags { display: flex; flex-wrap: wrap; - justify-content: flex-end; + justify-content: flex-start; gap: var(--space-xs); - flex-shrink: 0; + min-width: 0; } .library-highlight-carousel__description { diff --git a/templates/v3/includes/_library_highlight_carousel.html b/templates/v3/includes/_library_highlight_carousel.html index 7824b9ee8..910ee7eb4 100644 --- a/templates/v3/includes/_library_highlight_carousel.html +++ b/templates/v3/includes/_library_highlight_carousel.html @@ -50,6 +50,15 @@ aria-label="Show {{ slide.name }}" {% if forloop.first %}checked{% endif %}> {% endfor %} + {% else %} + {% endif %}