Darbase
Book a demo
Back to all posts

July 22, 2026 · 5 min read

Filters that survive a refresh

We shipped saved views recently, and the part worth writing about is not the feature. It is what we had to fix first.

The filters were not real

Our inbox had six filters — status, channel, source, grade, assignee, stage — and every one of them lived in React state. They worked. You could combine them, the list responded, everything looked fine.

And then you refreshed the page and they were gone. You could not bookmark a filtered list. You could not send one to a colleague. You could not open the same view tomorrow morning without rebuilding it.

That is not a small gap in polish. A filter you rebuild every morning is a filter you eventually stop using, and a filter you cannot send to somebody is a filter that cannot be the subject of a conversation. "Look at the unassigned hot leads in Marina" is a sentence a sales manager wants to say with a link attached.

URL state first, saved views second

So the saved-views work started somewhere unglamorous: moving every filter out of useState and into the querystring.

The design rule we settled on is that a filter at its default value is deleted from the URL rather than written empty. A URL reading ?status=all&channel=all&grade=all is noise; ?grade=hot is a thing you can read at a glance and edit by hand.

Once filters were in the URL, a saved view became almost trivial: it is a name and a querystring. Applying one replaces every filter parameter and leaves everything else alone, so a saved view applied while you have a conversation open does not close the conversation.

Private by default, published deliberately

The second decision was about who can publish.

Views are private by default. A manager can publish one to the whole desk, and published views can only be edited by a manager. That asymmetry is deliberate: a view the desk relies on every morning should not silently change because one person adjusted a filter and re-saved.

Expressing that turned out to need application logic rather than a role decorator. "Members may write, but only managers may publish" is not something a minimum-role annotation can say. So the controller sits at member level and the distinction lives in the service, with a test proving both boundaries — across tenants, and within one tenant between private and shared.

The general lesson

If a piece of state in your product describes what the user is looking at, it belongs in the URL. Sort order, filters, tabs, the open section of a report, the step of a guided flow.

The test is simple: could somebody usefully send this to a colleague? If yes and they cannot, that is the bug — and it is usually a bigger one than whatever feature you were about to build on top of it.

Filters that survive a refresh