Designing by inversion · Replacing a broken Visual Studio tab saver
I've recently published the final version of Simple Tab Saver, a Visual Studio extension that saves and restores open document tabs, which I created as a side project. It started small, but turned into two things at once: a crash course in Visual Studio extension development from zero, and a months-long experiment in AI-assisted development on a real, growing codebase. Various discoveries, successes and mistakes along the way inspired me to create this series of articles, something like an engineering post-mortem.
The direct impulse to start this project was Workspace Manager stopping working in VS2022 version 17.9, and being broken far longer than I could stay on 17.8, which eventually forced me to look for a replacement, fruitlessly. Here's some background lore about why Simple Tab Saver exists, and how a broken predecessor shaped it. The reason it broke turned out to be more nuanced, and the closing section returns to it.
Being a tab person
I always work with lots of open tabs. I like my environment arranged and pinned in place, which eliminates wondering where an open tab went, because it is always in the same tab row, in the same position. While developing a large project, instant switching between the 30-40 tabs related to a specific area is the basis of how I work.

But before establishing that tab-arrangement-switching workflow, for many years I just kept one core set of tabs open and depended on Visual Studio to restore them on startup. It worked fine, until one day a corrupted workspace forced me to delete the .vs folder, and my carefully arranged tabs went with it, leaving me with an empty tab bar, which I had to rebuild by hand from memory.
Workspace Manager era
Annoyed, I went looking for an extension to solve this, and found Workspace Manager. It was good in exactly the way I wanted: a small dropdown on the toolbar, easy to use, doing only what was needed and nothing more, with no big tool window and no feature sprawl. I used it happily for years.
There were several painful limitations, though, and over time I understood where they came from. The saved tab arrangement was a BLOB in a SQLite table. It could not be inspected, offered no migration path between Visual Studio releases, and it stopped working entirely if a solution got renamed or moved (because the stored data used absolute paths to the open files). Later I learned why. Workspace Manager was essentially a thin wrapper around an internal Visual Studio interface called IVsUIShellDocumentWindowMgr, which produced the same binary stream Visual Studio itself used (at the time) to store the open-document list inside the .suo file, and the extension inherited every property of that stream: unreadable, absolute-pathed, release-coupled.
On top of that, renamed and moved files were an everyday pain of their own. When a file moved even slightly, the restored tabs completely lost track of it and I had to reopen everything by hand. In a large monorepo where other developers rename and relocate files or folders in their commits, this happened more often than I'd like, and ruined my carefully prepared tab arrangements.
Holding back the updates
Despite all the flaws, Workspace Manager did its job just fine, until one day it broke. The last Visual Studio release where it fully worked for me was VS2022 version 17.8. After that, Workspace Manager became unreliable, restoring wrong tabs and outright refusing to save new ones. The extension's author eventually filed a regression report against Visual Studio 17.9 on Developer Community, pointing at a bug in IVsUIShellDocumentWindowMgr, its underlying interface: ReopenDocumentWindows had started ignoring the stream passed to it and always restored whatever set the latest SaveDocumentWindowPositions call had captured.
I discovered the breakage myself after routinely updating Visual Studio, and it forced me to roll back the update, something I had never done before. This incident proved how important the tab-saving feature had become for me: I held off updating Visual Studio for almost two years, because updating meant losing Workspace Manager, and I hoped that it would get fixed. Voluntarily running an outdated IDE to protect a tab-saving dropdown is not a workflow preference anymore but a critical dependency.
Eventually holding back stopped being viable and I had to update for real. First I searched for a replacement. I wanted a compact, simple toolbar to save and restore tabs, and the search turned up either full tool windows built around a much larger feature set, or extensions that moved all the open documents out of the tab bar into a canvas of their own. Neither matched what I was looking for. So the plan became: just build it myself. I had never written a Visual Studio extension before, but how hard can it be, right? Turns out, it's harder than anticipated, but in different ways than I thought.

Doing the opposite
A tool replacing something that failed has the failure as its best design document. Every property of the broken extension got inverted into a principle.
| Workspace Manager | Simple Tab Saver |
|---|---|
| Opaque BLOB in a SQLite table | Plain JSON, one file per layout, editable by hand |
| Absolute file paths, broken by any move | Solution-relative paths, movable and shareable |
Single internal interface, IVsUIShellDocumentWindowMgr | Wide range of APIs and UI Automation, with fallbacks |
| Fixed storage location at the solution root | Configurable storage folder |
| Moved and renamed files silently dropped | Relocation detection with a pick-target dialog |
The old data was a BLOB. The new storage format in my extension is plain JSON, one file per saved tab layout, readable and editable by hand:
{
"ExtensionName": "Simple Tab Saver",
"SchemaVersion": "1.0",
"Name": "Work in Progress",
"ActiveTabFilePath": "Core.cs",
"Tabs": [
{ "Order": 0, "FilePath": "Core.cs", "IsPinned": false, "LineNumber": 7, "ColumnNumber": 1, "IsSelected": true },
{ "Order": 1, "FilePath": "Docs\\Notes.md", "IsPinned": false, "LineNumber": 54, "ColumnNumber": 1 },
{ "Order": 2, "FilePath": "Services\\Alpha.cs", "IsPinned": false, "LineNumber": 12, "ColumnNumber": 17 },
{ "Order": 3, "FilePath": "Readme.txt", "IsPinned": false, "LineNumber": 20, "ColumnNumber": 5 }
]
} The old data used absolute paths and died on any move. The new format stores solution-relative paths, and the whole storage folder is under the solution directory by default, so the solution can be moved, copied to another machine, or committed with its tab layouts to a shared repository, and everything keeps working. It also depends on nothing but file paths and a handful of integers, rather than being coupled to Visual Studio internals the way the old data was. Additionally, the old extension forced its storage file to a fixed location at the solution root, so an early addition to the new extension was an option to set an external storage folder.
Most important of all, the previous extension depended entirely on IVsUIShellDocumentWindowMgr, a single point of failure which proved fatal to it when the shell internals moved beneath that interface. The new extension uses a wide range of APIs and UI Automation, and implements multiple fallbacks, intentionally staying decoupled from it.
The rename problem of the predecessor, which I've mentioned earlier, became a first-class design concern instead of an accepted failure. The extension detects missing files on restore, searches for relocated and renamed candidates, and asks instead of silently dropping tabs. This could never happen inside a binary format Workspace Manager did not own. Here, it is a simple filesystem search and a saved-path adjustment. My oldest frustration with the previous tool became a feature in its own right.

The one principle I deliberately carried over from Workspace Manager unchanged is the compact toolbar: a dropdown and a few buttons. Whatever the extension grew into internally, the immediately visible part stays unobtrusive, because saving and loading tabs is still a niche feature and does not warrant an entire tool window for itself.
Inversion gave me the data format, and the rest of the project's shape came from rules I imposed before writing the first line: minimal external dependencies, plain and simple foundations, everything customizable but with sensible defaults, everything repetitive automated, and internals explored rather than trusted through a single interface. The automation rule alone grew into an in-process test framework that drives the extension inside a live experimental Visual Studio instance, because a feature only a human can verify by hand quickly becomes a feature nobody verifies. None of these rules is a technical necessity. They are self-imposed limitations chosen before they were needed, and almost every methodology in the later articles exists as the direct result of one of them.
Going all the way
The plan was a bare-bones save and restore tool, finished in one weekend. That is exactly what happened, and the first functional version with basic save and restore was ready after two days of work. The goal was achieved, a broken extension had its replacement, and by that measure the project was done. But it was not the end. The initial success only encouraged me to go deeper. The changelog currently has over 700 internal revisions, and the feature list includes floating window capture, tab groups, git-branch-aware tab layouts, breakpoint capture with self-correcting line anchors, autosave and autorestore, a quick switcher, multiple management dialogs, and an in-process automated test framework. The bare-bones plan did not survive.

Part of that is ordinary scope growth, because once restore fidelity is the goal, every remaining gap becomes a challenge. The more sincere reason is that somewhere early on, this stopped being primarily about tabs. I was using the project to learn Visual Studio extension development from zero, and more importantly to learn and fine-tune AI-assisted development workflows on a real, growing codebase over months, in a plain web-chat Claude setup (that will get its own article).
Before this project I was only using AI to generate snippets, stand-alone scripts, and boilerplate code, never to work on an entire project from scratch. The tab saver was almost coincidental: simply a problem I personally had, small enough to start, and deep enough to never run out of interesting challenges. A better learning vehicle would be hard to design: a domain with no documentation, a platform full of traps, and a user (me) with strong preferences and a daily need that this project meets.
All because I refused to live without a save/restore tabs dropdown.
The best (or worst) for last
The part that I find a little bit ironic, and the reason I saved it for last, is that Workspace Manager works again in Visual Studio 2026. The bug in IVsUIShellDocumentWindowMgr turned out to be a rework inside Visual Studio which moved the window management to a completely different data model internally, not a lasting defect in the interface itself. Microsoft's documentation records the moment: starting with version 17.9, the list of open documents moved from a binary format in the .suo file to a plain JSON file in the .vs folder. The author's bug report even caught what the transition looked like from outside: a replay call that ignores its own input and restores the shell's latest internal state is exactly the shape of machinery moving beneath a stable interface.

When I found that out while working on improving save and restore performance, I had already completed most of the Simple Tab Saver development, so it did not change a thing, and my extension's scope grew so far beyond its inspiration that going back would be a downgrade. The reason to avoid that interface has not changed either: it is an undocumented internal surface whose storage model has already been reworked once, and anything built directly on it inherits the next rework too.
But in the end, without Workspace Manager and the "bug" my project would not exist.