← back to Stars of Design
Hydrate saved sort BEFORE first grid render
18acbe8cf71cfdfd634509973ead48ef1cd84671 · 2026-05-19 15:17:57 -0700 · Steve Abrams
Per the standing rule: when a user has picked a sort in /designers or on
the home grid, that preference must apply on the very first render of a
fresh visit, not only after they touch the dropdown again. Server-side
sorting is driven by ?sort= in the URL, so this commit makes
public/js/grid-controls.js do a one-time replace() redirect to the saved
sort value when the URL has no sort param yet.
Guards against:
- cross-page contamination — only redirects if the saved value is one of
the options actually present in this page's <select> (so a "default"
saved from /clients won't crash /designers).
- redirect loops — gated by a sessionStorage flag.
- form-driven pages — /clients and /firms wrap their <select> in a <form
onchange="this.form.submit()">; the JS now defers to the form submit
instead of double-navigating.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M public/js/grid-controls.js
Diff
commit 18acbe8cf71cfdfd634509973ead48ef1cd84671
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue May 19 15:17:57 2026 -0700
Hydrate saved sort BEFORE first grid render
Per the standing rule: when a user has picked a sort in /designers or on
the home grid, that preference must apply on the very first render of a
fresh visit, not only after they touch the dropdown again. Server-side
sorting is driven by ?sort= in the URL, so this commit makes
public/js/grid-controls.js do a one-time replace() redirect to the saved
sort value when the URL has no sort param yet.
Guards against:
- cross-page contamination — only redirects if the saved value is one of
the options actually present in this page's <select> (so a "default"
saved from /clients won't crash /designers).
- redirect loops — gated by a sessionStorage flag.
- form-driven pages — /clients and /firms wrap their <select> in a <form
onchange="this.form.submit()">; the JS now defers to the form submit
instead of double-navigating.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
public/js/grid-controls.js | 54 ++++++++++++++++++++++++++++++++++++----------
1 file changed, 43 insertions(+), 11 deletions(-)
diff --git a/public/js/grid-controls.js b/public/js/grid-controls.js
index fc8de1a..fc0546e 100644
--- a/public/js/grid-controls.js
+++ b/public/js/grid-controls.js
@@ -1,5 +1,11 @@
// Density slider + sort select. Sort hits the server via querystring (matches lib/sort.js).
// Density adjusts CSS var --cols-min on the grid; both values persist in localStorage.
+//
+// Hydration order matters — per the standing rule, the saved sort MUST apply
+// BEFORE the first grid render. Server-side sort happens via the `?sort=` URL
+// param, so on page load we check: if URL has no sort param but a saved value
+// exists in localStorage, redirect once with the saved value as `?sort=`.
+// This makes the saved sort effective on the very first paint of a fresh visit.
(function () {
var KEY_SORT = 'sod-sort';
var KEY_DENS = 'sod-density';
@@ -8,10 +14,45 @@
var sortEl = document.querySelector('[data-sort]');
var densEl = document.querySelector('[data-density]');
+ // --- Sort: hydrate saved value BEFORE first render via one-time redirect ---
+ if (sortEl) {
+ try {
+ var savedSort = localStorage.getItem(KEY_SORT);
+ var url = new URL(window.location.href);
+ var urlSort = url.searchParams.get('sort');
+ // Only redirect if (a) we have a saved sort, (b) URL has no sort yet,
+ // (c) saved sort is actually one of the options on this page (don't
+ // cross-contaminate between /designers, /clients, /firms which each
+ // have different sort vocabularies). Also gate against loops with a
+ // sessionStorage flag.
+ if (savedSort && !urlSort && sortEl.querySelector('option[value="' + savedSort.replace(/"/g, '') + '"]')) {
+ var hydratedFlag = 'sod-sort-hydrated';
+ if (!sessionStorage.getItem(hydratedFlag)) {
+ sessionStorage.setItem(hydratedFlag, '1');
+ url.searchParams.set('sort', savedSort);
+ window.location.replace(url.toString());
+ return; // stop further setup; page will reload with the saved sort
+ }
+ }
+ } catch (e) {}
+
+ sortEl.addEventListener('change', function () {
+ try { localStorage.setItem(KEY_SORT, sortEl.value); } catch (e) {}
+ // sort selects inside a <form> already submit themselves (see clients.ejs
+ // / firms.ejs onchange="this.form.submit()"); for the standalone partial
+ // (home, /designers) we drive navigation here.
+ if (sortEl.form) return; // form submit handles it
+ var u = new URL(window.location.href);
+ u.searchParams.set('sort', sortEl.value);
+ window.location.href = u.toString();
+ });
+ }
+
+ // --- Density: pure client-side, persist + apply CSS var ---
if (densEl && grid) {
try {
- var saved = localStorage.getItem(KEY_DENS);
- if (saved) { densEl.value = saved; }
+ var savedDens = localStorage.getItem(KEY_DENS);
+ if (savedDens) { densEl.value = savedDens; }
} catch (e) {}
grid.style.setProperty('--cols-min', densEl.value + 'px');
densEl.addEventListener('input', function () {
@@ -19,13 +60,4 @@
try { localStorage.setItem(KEY_DENS, densEl.value); } catch (e) {}
});
}
-
- if (sortEl) {
- sortEl.addEventListener('change', function () {
- try { localStorage.setItem(KEY_SORT, sortEl.value); } catch (e) {}
- var url = new URL(window.location.href);
- url.searchParams.set('sort', sortEl.value);
- window.location.href = url.toString();
- });
- }
})();
← e85f238 Makefile: audit target
·
back to Stars of Design
·
Add noreferrer to all rel=nofollow noopener external links a7ac2a9 →