Skip to content

Story 2386: Library Page Integration#2420

Open
julhoang wants to merge 23 commits into
developfrom
julia/library-page-integration
Open

Story 2386: Library Page Integration#2420
julhoang wants to merge 23 commits into
developfrom
julia/library-page-integration

Conversation

@julhoang
Copy link
Copy Markdown
Collaborator

@julhoang julhoang commented May 12, 2026

Issue: #2386 , #2409

Summary & Context

Ships interactive filter, fuzzy token search (Fuse.js), sort, and empty state designs on the V3 Libraries Page. Adds a new cpp_standard_maximum model field so the C++ version filter can enforce a min/max range.

Filter state lives in an Alpine component, syncs to the URL, and operates on a compact dataset embedded once per page load — no per-keystroke server requests.

Changes

Library page filtering

New UI (_library_filter.html, library_page.html)

  • Add clear (X) button to filter controls
  • "Most Popular" sort ordering by Tier (Flagship → Core → Deprecated → Legacy)
  • Clear All button that only renders when filters are active; resets all fields except View
  • Empty state (_library_empty_state.html) with light/dark illustration; distinguishes "no search match" from "no filter match"
  • <noscript> fallback hides JS-dependent controls and force-shows x-cloak'd sections so the page still renders

State & wiring

  • Inline libraryFilter() Alpine component owns state; syncUrl() hydrates from and writes back ?grading=…&min_cpp=…&max_cpp=…&category=…&q=…&sort=…
  • LibraryListBase.get_v3_context_data centralizes filter fields, defaults, the dataset payload, search query, and Clear All URL
  • Grading filter only enumerates tiers actually present in the queryset

V3 field include extensions

  • _field_dropdown.htmldeselectable clear button, field-disabled-values listener (powers min ≤ max C++), field-set rebroadcast, dropdown--has-value accent
  • _field_combo_multi.html — per-tag and clear-all buttons, clear-all-filters listener, MutationObserver-friendly hidden inputs
  • _field_text.html (used for Search bar) — dispatch_field_change with 150 ms debounce

Fuzzy search (Fuse.js)

  • Vendored [email protected] bundled via esbuild (frontend/fuse-entry.jsstatic/js/v3/fuse.min.js) with yarn build:fuse
  • Loaded with defer plus a DOMContentLoaded fallback in init(); if ?q= is in the URL when the late build completes, apply() re-runs so deep links work
  • Hero search → 150 ms debounced field-changequery state → runSearch()fuse.search(q), results keyed by slug
  • Score-based sort overrides the default Sort dropdown only when there's an active query; an explicit user sort always wins
  • Index built once from {{ library_dataset|json_script:"library-data" }} (slug, name, description, categories, authors, cpp_min/max, tier)

Fuse options

  • useTokenSearch: true — multi-word queries match regardless of token order
  • ignoreLocation: true — no positional penalty
  • threshold: 0.3 — moderate fuzziness (0 = exact, 1 = match anything)
  • minMatchCharLength: 2 — drop single-character matches
  • includeScore: true — per-result score for relevance ordering
  • Weighted keys: name (3) > category_names (1.5) > author_names (1) = description (1)

Max C++ version support

Model & import

  • New LibraryVersion.cpp_standard_maximum field (nullable, choices=CPP_STANDARDS) + migration 0040_libraryversion_cpp_standard_maximum
  • parse_libraries_json (core/githubhelper.py) reads cxxstd_max; versions/tasks.py assigns it on import, mirroring the existing cxxstd flow
  • get_cpp_standard_maximum_display() paralleling the minimum getter (with unit test)
  • ❗️ C++23 added to accepted versions; refreshed broken upstream docs link (old broken link here -> new link here)

Filter behavior

  • Two dropdowns (Min. C++ Version, Max. C++ Version); both values in the dataset as cpp_min / cpp_max
  • Min ≤ max enforced client-side: changing either dispatches field-disabled-values to the other; invalid pairs from URL params auto-correct on load
  • Predicate treats each library's [cpp_min … cpp_max] as a range and passes if it overlaps the selected window (e.g. a C++14–20 library matches a Min=C++17 selection)

Risks & Considerations

  • Sort is intentionally client-side. Non-JS clients see only the server's alphabetical order (ordering = "library__name" on LibraryListBase).
  • Progressive enhancement scope. With JS off, only the View dropdown and the rendered list/grid remain; the search bar, Clear All, and non-View filters are hidden. Crawlers still index the full server-rendered list.
  • Fuse.js load ordering. Loaded with defer; init() builds the index immediately if available, otherwise retries on DOMContentLoaded. A ?q= in the URL triggers a re-apply after the late build so deep links still work.
    • Migration safety. 0040 adds a nullable cpp_standard_maximum field — safe forward and reversible. Existing libraries without cxxstd_max in the JSON simply read None and display nothing for the max bound.
  • The Saturday 8:03 PM release_tasks Celery chain (also triggerable from Django Admin → "Update Libraries", and on every new release/beta import) reads each library's meta/libraries.json via parse_libraries_json and writes cpp_standard_maximum to the matching LibraryVersion row in import_library_versions. The parser uses libraries_json.get("cxxstd_max") so the field is read opportunistically — if upstream adds cxxstd_max to a library's JSON, it flows in on the next run with no code change; if absent, the field stays None. In the meantime the value is editable directly on the LibraryVersion change form in Django Admin.
Screenshot 2026-05-12 at 4 22 10 PM

Screenshots

Desktop Mobile No-JS Version
Screenshot 2026-05-12 at 4 27 35 PM image image

Self-review Checklist

  • Tag at least one team member from each team to review this PR
  • Link this PR to the related GitHub Project ticket

Frontend

  • UI implementation matches Figma design
  • Tested in light and dark mode
  • Responsive / mobile verified
  • Accessibility checked (keyboard navigation, etc.)
  • Ensure design tokens are used for colors, spacing, typography, etc. – No hardcoded values
  • Test without JavaScript (if applicable)
  • No console errors or warnings

Copy link
Copy Markdown
Collaborator

@julioest julioest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Great work on this! Pre-approving, with one inline comment to address before merge.

Comment thread templates/v3/includes/_field_dropdown.html Outdated
Copy link
Copy Markdown
Collaborator

@jlchilders11 jlchilders11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks for all of the work!

Copy link
Copy Markdown
Collaborator

@herzog0 herzog0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heya! Amazing work!!
I just have one note about the min/max logic, which I think should be discussed with Rob.

Pre-approving though 🚢

Comment on lines +344 to +345
if (this.maxCpp !== 'all' && data.cpp_min && cppRank(data.cpp_min) > cppRank(this.maxCpp)) return false;
if (this.minCpp !== 'all' && data.cpp_max && cppRank(data.cpp_max) < cppRank(this.minCpp)) return false;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this only works when the cpp_max version gets populated, and that will require a tremendous amount of work on the Cpp Alliance side (having to reach out to every developer, asking what's their maximum supported version, which may not be an easy task for them to find out as well), I think we should play safe and, if there's no max version reported for the library, we don't show it if its cpp_min is less than the selected value.

I mean adding a filter like if (this.minCpp !== 'all' && data.cpp_min && !data.cpp_max && cppRank(data.cpp_min) < cppRank(this.minCpp)) return false;.

Because if the user trusts the website blindly and a lib's max version isn't populated, they might introduce a bug in their project.

Either that or we be more explicit in the UI, adding a new column and renaming the current one to "Min Version" and "Max Version" (which I actually think should be added anyway).

Comment thread templates/v3/includes/_library_item.html Outdated
Comment thread templates/v3/includes/_library_empty_state.html
@julhoang julhoang force-pushed the julia/library-page-integration branch from 1c2d358 to 47f6bb5 Compare May 19, 2026 21:51
@herzog0 herzog0 mentioned this pull request May 19, 2026
9 tasks
@julhoang julhoang force-pushed the julia/library-page-integration branch from 47f6bb5 to 636db41 Compare May 21, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Task: Filter Component Updates Webpage Integration: Library

5 participants