Deep-link Search By mode via URL hash - #1311
Conversation
Makes the Search By target (general/author/title/series/manual) and content type live in a #hash fragment instead of pure client state, so users can bookmark/share a link that opens straight into a mode. The hash updates live via history.replaceState (no history spam), and the last-used Search By target is remembered in a client-side cookie as the default when a link doesn't specify one.
UrlSearchBootstrapMount applies the search_by hash override once, on mount, but queryTargets can still be missing the hashed target at that point if the metadata search-fields fetch hasn't resolved yet. Since the mount effect never re-runs, the override was silently dropped and the cookie-seeded default stuck. Add an independent effect in App.tsx that retries the override whenever queryTargets changes, until it succeeds (or the target never becomes valid). Kept separate from the one-shot bootstrap so the search execution / advanced filters logic still only fires once.
|
Hey, thank you so much for the contribution ! 1. Existing query-string deep links break, and the docs still document them
The hash was the right call for the new live-reflected state, but I don't think we want to drop what already shipped. Could we parse the query string as a fallback when the hash is empty — ideally rewriting 2. Non-general Search By targets don't round-trip: the hash the app writes reopens as a blank or general search This is the headline use case, and I think it's broken for every target except
I think this needs fixing on both sides: have 3. The cookie is written from the derived fallback, not the user's choice
Persisting only on an explicit selector change ( 4.
Worth a look5. Pasting a shared link into an already-open tab does nothing. A hash-only change doesn't reload the document and there's no 6. The Nits
|
Review follow-ups on the deep-link work. Round-tripping: the hash carried `searchInput`, but that only holds the value for general/direct/text targets - a provider field like `series` keeps it in searchFieldValues, so `#search_by=series&q=dune` reopened as a plain general query with the selector reading Series. The hash now carries the active target's value as `q`, and the bootstrap routes it back the way the live dispatch does: direct fields into the search input and their own filter slot, provider fields as fieldValues, and `manual` fills the input without auto-searching (it opens the release browser from an explicit submit). Legacy links: `?q=…` query strings shipped before the hash and are documented, so they're still read when the hash is empty, and rewritten to `#…` once on load so the two can't drift. Stored default: the cookie was written from the re-validated `effectiveActiveQueryTarget`, which collapses to `general` on a cold load before the search fields resolve, on the logo reset, on logout, and is set programmatically by a `view_series` browse - each of which quietly destroyed the user's stored preference. It's now written only when the user picks a target in the selector, and lives in localStorage rather than a cookie so it isn't sent on every request. Write volume: `replaceState` fired per keystroke, and Safari throws SecurityError past ~100 calls per 30s. Debounced to 300ms and wrapped in try/catch. Also: a pasted link now re-bootstraps via `hashchange` instead of doing nothing; the bootstrap waits for the search fields to settle so a deep link can't run against the wrong target and then rewrite the shared hash without `search_by`; and `search_by` keeps its casing, matched case-insensitively against the live targets, so a custom provider's camelCase field key resolves.
|
thank you so much ! |
|
@calibrain happy to help, I use Shelfmark pretty much daily so this one was for me too. Thanks for the quick merge. One thing I noticed while testing that wasn't in your review: the hash picks up the default filters, so a plain author search ends up as |
|
sure, no use keeping empty / default values in the url ! |
Closes #1228.
Search By mode (General/Author/Title/Series/Manual), content type and the search query now live in the URL as a hash fragment, updated live as you search, not just parsed once on load. A shared/bookmarked link like
#search_by=manual&q=foundationreopens in that exact mode with the query filled in.Following the direction from the issue thread:
history.replaceState, no history spam per keystroke.Tested manually against a local build (search-by switching, hash live update, deep link reload, cookie fallback when there's no hash, and hash overriding a stale cookie).