Notes2026.04.01
A model can guess my projects. Gruper checks before it moves a tab.
I built a browser extension that sorts my tabs by project. A lot of it is about checking the model before it touches a tab.
- Status
- Published
- Date
- 2026.04.01
One project of mine can end up in four places at once: a doc, a Figma file, a GitHub repo and a handful of research tabs. The browser has no idea they’re related. Grouping by site pulls them apart, and grouping by topic isn’t much better, because two different projects can easily share a topic.
I wanted my browser sorted by the work I’m doing, so I built Gruper. It’s an extension for Chromium browsers that tries to figure out which project each tab belongs to, groups the tabs, and remembers when I correct it.
The tricky part is that the window doesn’t sit still while the model thinks. I might close a tab, drag a group somewhere else or open something new, and by the time an answer comes back, it can be describing a window that isn’t there anymore. So a lot of Gruper is about checking what the model says before acting on it, in a way I can follow and undo. Building it made me see how much of an AI tool lives around the model: what it gets to see, what it’s allowed to suggest, how that gets checked and how I fix its mistakes without losing control of my window.
Plan, check, then move
Every run happens in two steps. First Gruper makes a plan and touches nothing. The plan says which tabs go into new groups, which join groups I already have, and which stay where they are, and every tab gets exactly one answer. Then, right before acting, Gruper checks the plan against the window as it is now. Only if it still fits does anything move.
The background service worker runs the model, holds the queue and moves the tabs, and the toolbar panel only shows progress and sends requests. With one place in charge, a manual run, an automatic run and a quality check can’t fight over the same tabs. (It’s Vue and WXT, if you’re curious.)
Sometimes the right answer is “leave it”
Gruper never reads what’s on the page. It only sees tab titles, a few hints from the URL, where each tab sat in the window, the groups that already exist and which tab opened which.
None of that is reliable on its own. Two tabs next to each other can belong to different projects, and two docs on the same site can have nothing to do with each other, so sharing a domain or sitting side by side doesn’t count for much. Two things do count as strong evidence: a match with an example I’ve approved, or the project named outright. Anything weaker needs backup before Gruper acts on it, like a matching word in the title or the URL path, plus the tabs being close together or one having opened the other.
I made sure Gruper is allowed to leave a tab ungrouped. I’d take a half-sorted window I can trust over a tidy group with a stranger in it.
Big windows get cut into smaller chunks, each with a few of the neighboring tabs for context. Afterwards Gruper merges groups that turn out to be the same project, with checks that stop two different projects from getting glued together.
Even a confident model has to show its work
The local version runs Qwen models in the browser, with WebLLM on WebGPU. One engine loads when it’s needed and unloads after it’s been idle for a while. The WASM files ship with the extension, and the model weights download separately.
Later I added a hybrid route. Qwen3.5-4B comes up with project names on my machine, and TypeSafe’s Jev picks from a fixed list of candidates for the tabs that are still undecided. If a tab clearly matches something already in memory, it doesn’t need a cloud request at all.
I don’t treat any model answer as an order, even a clean one. Gruper checks that it’s shaped right, that it covers every tab and that each choice is valid. On the Jev route, anything under 0.70 confidence stays ungrouped, and a high score doesn’t let a tab skip the evidence checks either.
It learns from my corrections
I wanted it to get better every time I fix it, so it keeps a small project memory in chrome.storage.local: a fixed ID for each project, the other names it goes by, and examples of tabs that belong and tabs that don’t. That ID isn’t the browser’s group ID, so a project is still remembered after its group is gone.
Moving a tab teaches it twice. When I drag a tab out of a group Gruper recognized and into another project, it learns where the tab belongs and where it doesn’t. What the model guessed doesn’t turn into an example on its own, though. Otherwise Gruper would end up treating its own guesses as proof. All of this is memory and context, and the model’s weights never change.
Undo shouldn’t undo me
Undo was one of the hardest parts to get right. Say Gruper sorts my window and then I move a tab myself. If I hit Undo, Gruper’s changes should roll back, and my move should stay.
So before it touches anything, Gruper saves a snapshot of the window, and before every change it checks the window still looks the way it expects. A local log keeps track of what the run meant to do and how far it got. Undo reads that log and puts tabs and groups back carefully, taking into account pages I’ve navigated and changes I made later. When it can only undo part of a run, it tells me, since the browser can stop between making a change and saving its checkpoint.
Pinned tabs, tabs I’ve excluded and the browser’s own pages are off limits, and nothing ever moves to another window.
What leaves my machine
In local mode, the thinking happens on the device. The hybrid route sends a small, fixed set of things: short titles, filtered URL hints, positions and the candidate names, all under temporary IDs. It never sends my saved examples, the fixed project IDs, URL fingerprints or page content.
A short title or a project name can still be sensitive on its own, though, so the hybrid route is not as private as staying local.
What the tests do and don’t tell me
I kept three kinds of tests apart: logic, browser and model quality. Tests with fake model answers can prove Gruper throws out a broken plan, and tests in Helium check real group actions, saving corrections and Undo. Neither can tell me whether the model understands my projects.
For that I compared models on five synthetic windows. Qwen3.5-9B passed the quality metrics. The 4B missed some of the naming and group-splitting checks, and the older 3B ran out of answer length with the new planner.
Those are test scenarios, though. Whether Gruper sorts my real work window well is still open, and to find out I need a set of groupings I’ve checked by hand.