How a filter is applied

This is the page to read before you tell a customer "Gerry filtered my list but the call just dropped." The short version: a normal page load ends the call, so Gerry never does one — until something makes it impossible not to.

Why not just reload the page

Measured on our own sessions collection: the voice provider ends a call the instant the browser participant leaves the room, and every call after a navigation is closed by the provider's own report a few seconds later (endReason: 'provider_report'). The room token lives 60 seconds, so even an instant reload cannot rejoin it. A reload is the end of the conversation, not a pause in it — there is no page-load event on the other side for the assistant to pick up in.

What happens instead, in order

The engine decides the address exactly as it would for a normal link — the map's parameters, path segments, and the site's own facet links, all unchanged — and then, instead of navigating to it:

  1. fetch that address: same-origin, a plain GET, the visitor's own cookies, no headers of ours (one would trigger a CORS preflight and let a CDN's Vary hand back a page the browser itself would never see). Gives up after six seconds.
  2. Parse the answer with DOMParser.
  3. Replace the results region — and the filter panel too, if the map opted in — with the fetched page's own markup for it.
  4. history.pushState the address, so the URL bar, sharing and the Back button all behave, and adopt the fetched page's <title>.
  5. Dispatch a gerry:navigate event on window, so your own page script can react — there is no load event this time.
  6. Read the count and the rows off the now-live document, and answer the model with them.

Back and forward do the same fetch-and-swap, from a popstate listener. Nothing is ever half-swapped: every replacement node is resolved and ready before anything on the page is removed.

The demo store showing all twelve products, before a category filter is spoken
Before: "show me headphones" hasn't been said yet.
The demo store after the category filter narrowed the list to four headphones, with no page reload
After: the same page, the results region swapped in place. The call is still open — this is what "soft" buys you.

When it falls back to a real page load

Anything that goes wrong falls back to location.assign — the behaviour the widget had before soft navigation existed, resume from sessionStorage and all. The ladder, checked in this order:

map_says_reloadThe map's navigation is set to reload — see below.
cross_originThe address, or where a redirect points, is off site.origin.
bad_statusThe fetch didn't come back 2xx.
not_htmlThe response wasn't text/html.
no_swap_targetA region the map wants swapped is missing from either the current document or the fetched one.

When a fallback happens, the conversation tries to continue on the new page with the same token; the visitor may need one more click on Gerry to restore audio. This is exactly the resume mechanism the widget always had — it just now only runs when soft navigation genuinely can't.

What your search page needs to look like

Two map fields decide what gets swapped, and getting them right is almost the whole job:

If your page reads location.search or location.pathname to decide what to render — most do — add one listener for gerry:navigate (and, for completeness, popstate) that re-reads the address and repaints, synchronously. The count Gerry says out loud is read the moment that handler returns, so anything asynchronous there is read too early.

When to declare navigation: "reload" instead

Say so when your raw HTML simply doesn't contain the results: a single-page app, a list hydrated client-side, a grid fetched by your own XHR after the page loads. There is nothing in the fetched document for the engine to swap, so every filter would be refused anyway — declaring reload up front skips the wasted request and the six-second timeout on every single filter. It's also the honest choice for a results region that genuinely can't be replaced without breaking the page: listeners bound per row with no delegation, a chart or a map widget sitting among the results, an <iframe> that must not be re-created. A map that says reload keeps the old behaviour, and with it the old limitation: the call ends at the first filter, same as any voice widget that reloads the page.

Opening a listing

pages.detail marks a single-listing page, never navigated to in place as a search result would be. pages.openIn decides how open_listing gets there: page (default) uses the same soft navigation, so the conversation survives and Gerry can describe the page it just opened; newTab is for a detail template that can't be swapped in, and keeps the call running on the search page the visitor started from.

A single product's detail page opened by voice, shown beside the still-filtered results list
"Open the first one" — open_listing with openIn: 'page' on the demo store.

Questions: hello@heygerry.io. These pages describe what is built today; nothing here is a promise about what is not.