[object Object]

← back to Designerlookbooks

persist sort + density to localStorage on lookbook and search pages

2a95466d905277f3cbb347e02b02c03494f7e916 · 2026-05-19 15:20:04 -0700 · Steve Abrams

Standing rule: every product grid must have sort + density controls
whose value persists across reloads, and the saved value must hydrate
BEFORE the first grid render. The lookbook + search pages already had
sort + density UI, but only density was persisted; sort was never
written to localStorage and never restored. Fix:

- Sort onchange handler now writes dl-sort to localStorage before reload.
- New hydration IIFE on both pages: when URL has no sort param and a
  saved sort exists (and it isn't the server default 'newest'), redirect
  once via location.replace so the server can re-sort the grid in the
  user's chosen order. Density still hydrates inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 2a95466d905277f3cbb347e02b02c03494f7e916
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 19 15:20:04 2026 -0700

    persist sort + density to localStorage on lookbook and search pages
    
    Standing rule: every product grid must have sort + density controls
    whose value persists across reloads, and the saved value must hydrate
    BEFORE the first grid render. The lookbook + search pages already had
    sort + density UI, but only density was persisted; sort was never
    written to localStorage and never restored. Fix:
    
    - Sort onchange handler now writes dl-sort to localStorage before reload.
    - New hydration IIFE on both pages: when URL has no sort param and a
      saved sort exists (and it isn't the server default 'newest'), redirect
      once via location.replace so the server can re-sort the grid in the
      user's chosen order. Density still hydrates inline.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 server.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 51 insertions(+), 11 deletions(-)

diff --git a/server.js b/server.js
index 8b4bd4b..2b8b6ea 100644
--- a/server.js
+++ b/server.js
@@ -702,7 +702,7 @@ app.get('/lookbook/:slug', (req, res) => {
     <div class="toolbar" id="grid">
       <div class="group">
         <label for="sort">Sort</label>
-        <select id="sort" onchange="(function(s){var u=new URL(location);u.searchParams.set('sort',s.value);u.searchParams.delete('page');location=u.toString();})(this)">
+        <select id="sort" onchange="(function(s){try{localStorage.setItem('dl-sort',s.value);}catch(e){}var u=new URL(location);u.searchParams.set('sort',s.value);u.searchParams.delete('page');location=u.toString();})(this)">
           ${[
             ['newest', 'Newest'],
             ['color', 'Color'],
@@ -754,15 +754,30 @@ app.get('/lookbook/:slug', (req, res) => {
       </nav>` : ''}
     </main>
     <script>
-      // Restore density preference if user saved one previously and the URL didn't override.
-      try {
-        var saved = localStorage.getItem('dl-cm');
-        var hasUrl = new URL(location).searchParams.get('cm');
-        if (saved && !hasUrl) {
-          document.documentElement.style.setProperty('--card-min', saved + 'px');
-          var el = document.getElementById('cm'); if (el) el.value = saved;
-        }
-      } catch (e) {}
+      // Hydrate sort + density preferences from localStorage when the URL
+      // doesn't already specify them. Sort needs a server round-trip
+      // because the grid is server-rendered — redirect once with the
+      // saved value so the page renders in the user's chosen order.
+      (function() {
+        try {
+          var url = new URL(location);
+          var qs = url.searchParams;
+          var savedSort = localStorage.getItem('dl-sort');
+          var validSorts = ['newest','color','style','sku','title','price-up','price-down'];
+          if (savedSort && validSorts.indexOf(savedSort) >= 0 && !qs.get('sort') && savedSort !== 'newest') {
+            qs.set('sort', savedSort);
+            location.replace(url.toString());
+            return;
+          }
+          var savedCm = localStorage.getItem('dl-cm');
+          if (savedCm && !qs.get('cm')) {
+            document.documentElement.style.setProperty('--card-min', savedCm + 'px');
+            var grid = document.querySelector('.grid');
+            if (grid) grid.style.setProperty('--card-min', savedCm + 'px');
+            var el = document.getElementById('cm'); if (el) el.value = savedCm;
+          }
+        } catch (e) {}
+      })();
     </script>
   `;
   res.set('Content-Type', 'text/html; charset=utf-8').send(layout({
@@ -837,7 +852,7 @@ app.get('/search', (req, res) => {
     <div class="toolbar">
       <div class="group">
         <label for="sort">Sort</label>
-        <select id="sort" onchange="(function(s){var u=new URL(location);u.searchParams.set('sort',s.value);u.searchParams.delete('page');location=u.toString();})(this)">
+        <select id="sort" onchange="(function(s){try{localStorage.setItem('dl-sort',s.value);}catch(e){}var u=new URL(location);u.searchParams.set('sort',s.value);u.searchParams.delete('page');location=u.toString();})(this)">
           ${[
             ['newest', 'Newest'],
             ['color', 'Color'],
@@ -882,6 +897,31 @@ app.get('/search', (req, res) => {
         ${cur < pages ? `<a href="?${new URLSearchParams({ ...req.query, page: cur + 1 }).toString()}">Next ›</a>` : ''}
       </nav>` : ''}
     </main>
+    <script>
+      // Hydrate sort + density from localStorage when the URL doesn't
+      // already specify them. Mirrors the lookbook page so user choice
+      // sticks across the whole storefront.
+      (function() {
+        try {
+          var url = new URL(location);
+          var qs = url.searchParams;
+          var savedSort = localStorage.getItem('dl-sort');
+          var validSorts = ['newest','color','style','sku','title'];
+          if (savedSort && validSorts.indexOf(savedSort) >= 0 && !qs.get('sort') && savedSort !== 'newest') {
+            qs.set('sort', savedSort);
+            location.replace(url.toString());
+            return;
+          }
+          var savedCm = localStorage.getItem('dl-cm');
+          if (savedCm && !qs.get('cm')) {
+            document.documentElement.style.setProperty('--card-min', savedCm + 'px');
+            var grid = document.querySelector('.grid');
+            if (grid) grid.style.setProperty('--card-min', savedCm + 'px');
+            var el = document.getElementById('cm'); if (el) el.value = savedCm;
+          }
+        } catch (e) {}
+      })();
+    </script>
   `;
   res.set('Content-Type', 'text/html; charset=utf-8').send(layout({
     title: q ? `Search: ${q}` : 'Search',

← 8a72dd2 add noreferrer to all target=_blank external links  ·  back to Designerlookbooks  ·  404-guard snapshot/backup paths and broaden .gitignore 2764ec6 →