js/url-state.js — search in the URL
In the code column — js/url-state.js. It writes the search into the URL: #/gallery?q=moon. So a link "remembers" what you saw, Back steps through your searches, and a refresh doesn't drop your filters.
The gallery route reads the params on arrival, and the search box writes them:
// arriving at the gallery — restore from the URL:
const { q } = getParams();
if (q) { setSearchQuery(q); searchInput.value = q; }
// while typing — write into the URL:
searchInput.addEventListener('input', debounce((e) => {
setSearchQuery(e.target.value.trim());
setParams({ q: e.target.value.trim() });
}));
Two decisions here — both traps if you get them wrong.
Trap 1 (replaceState, not location.hash =). setParams uses history.replaceState, which replaces the current history entry instead of adding a new one:
history.replaceState(null, '', `#${path}?${query}`); // ✅ replace in place
// location.hash = ... // ❌ pushes an entry PER keystroke
If you wrote location.hash = ..., every search keystroke would push a new history entry. Type "moon" — 4 entries. The user presses Back expecting to return to the start, and instead "erases" one letter at a time. Back becomes worthless. replaceState fixes it: search changes the URL but doesn't pollute history.
Trap 2 (URLSearchParams, not manual splitting). Never split a query (q=moon&days=30) yourself with split('&') and split('='). Values contain &, =, spaces, unicode — your splitting will break sooner or later. URLSearchParams encodes/decodes it all correctly:
new URLSearchParams('q=deep%20space&days=30').get('q'); // "deep space"
Verify.
1. Search "moon" in the gallery → the URL becomes #/gallery?q=moon. Copy it, open in a new tab → the gallery is already filtered.
2. Browse gallery → a picture's detail → press Back → you return to the gallery. Works on its own.
3. Deep link: #/apod/<date> opened fresh → the detail view loads itself.
Next. You've just written a router by hand — and that's exactly why you'll understand what a real one (React Router, Vue Router) does for you: hash/history management, params, deep links, Back. This ends Part 2: you have a full, real vanilla app. In Part 3 (lesson 15) we point the TypeScript compiler at it — and it finds real bugs we never noticed (like
detail.js'spic.hdurlandpic.copyright, which don't always exist).