Make library search and category filters deep-linkable#50
Conversation
The homepage filter state lived only in memory, so a filtered or searched view could not be shared or bookmarked, a reload dropped it, and the browser back button did not restore it. Reflect the search query, active category, and page in the URL query string (q, category, page), omitting defaults to keep clean URLs tidy. On load and on popstate, restore that state from the URL, validating the category against the available filters and clamping the page. This makes filtered views shareable and bookmarkable and gives back/forward the behavior visitors expect, which also suits an SEO- and agent-oriented catalog where linking directly to a slice of the library is useful.
|
This one's a feature rather than a bug fix, so completely understand if it's not a direction you want — feel free to close it, no hard feelings. The idea: the homepage filter state (search, category, page) lives only in memory, so a filtered view can't be shared or bookmarked, a reload drops it, and back/forward don't restore it. This reflects it in the URL ( If you're open to it but want it scoped differently (e.g. query-only, no page param), I'm glad to trim. Check suite + builders pass with no artifact drift. |
mberman84
left a comment
There was a problem hiding this comment.
The URL state is bookmarkable, but three blocking issues remain:
syncUrlState()starts with a freshURLSearchParams, so loading?utm_source=newsletter&q=agentimmediately deletesutm_sourceand any other URL state the library does not own. Initialize fromwindow.location.search, then set/delete onlyq,category, andpage.- Every interaction uses
replaceState. Browser verification confirmed that after changing category or page, Back leaves the library instead of restoring the prior filter state, so the newpopstatehandler cannot deliver the behavior claimed by the PR. UsepushStatefor meaningful category/pagination navigation boundaries; reservereplaceStatefor initial canonicalization or transient search typing. - This changes
site/script.jswithout bumping the shared versioned script URL. Update the emitted/validated asset version and regenerate affected pages.
… version Address review on the deep-linkable filters: 1. syncUrlState() now initializes from window.location.search and only sets/deletes q, category, and page, so unrelated parameters such as utm_source survive instead of being dropped. 2. Category and pagination changes now use pushState so the back button restores the prior filter state; replaceState is reserved for initial canonicalization and transient search typing. 3. Bump the shared script.js asset version to 20260621-url-state across the homepage, Learn/Agents pages, the loop-page generator, and scripts/check.mjs, then regenerate the loop pages.
|
All three addressed:
|
|
@mberman84 ready for re-review whenever you have time — foreign params preserved, |
Summary
The homepage library's filter state (search query, active category, page) lived only in memory. As a result:
This wires that state into the URL query string so a slice of the library is directly linkable — a natural fit for a catalog that's explicitly built for SEO/GEO and agent discovery (
llms.txt,catalog.json, etc.).Behavior
?q=…, category →?category=engineering, page →?page=2. Defaults (empty query,all, page 1) are omitted so the canonical homepage URL stays clean.popstate, state is restored from the URL. The category is validated against the available filter chips (unknown values fall back toall) and the page is parsed/clamped by the existing pagination logic, so hand-edited or stale URLs degrade gracefully.history.replaceState(guarded for environments without it), so typing in the search box doesn't flood the history stack.Implementation notes
applyCategory()(used by both the click handler and URL restore).syncUrlState()is called at the end ofupdateLibrary(), which already runs on every search/category/pagination change, so there's a single write path. It only callsreplaceStatewhen the URL actually changes.searchInput/results guards already short-circuitupdateLibrary).Verification
node scripts/check.mjs→Loop Library checks passed.node --check site/script.jsbuild-skill-catalog,build-loop-pages,build-social-images) → no artifact drift.?category=bogus/?page=99normalize to a valid state.