← All articles

Four Studios, One Platform: Inside the VS Codium Extension Suite

In my last post, I went deep on Prompt Workbench's CQRS architecture and the multi-provider AI router underneath it. This post covers the next thread from the overview article: the VS Codium extension suite — four studios and the shared platform layer that keeps them from each becoming their own tangled monolith.

The White-Label Pivot

Every studio in this suite started life as a standalone idea — a SaaS app with its own hosting, its own auth, its own deploy pipeline. Somewhere around early June this year, I stepped back and asked myself a question I'd recommend any solo builder ask before committing to a fourth or fifth standalone product: who is actually going to install and pay for four separate hosted apps from a one-person shop, each with its own login?

Nobody, realistically. So I pivoted the whole suite to a white-label VS Codium extension model instead — same product ideas, same underlying code, delivered as installable extensions inside an editor developers already have open all day, rather than as yet another tab in a browser. That pivot is why the directory structure you'll see if you go looking is extensions/studios/* and extensions/platform/*, not apps/*. It also forces a discipline I actually like: an extension that can't work offline on a laptop with no network connection is a broken extension, not a degraded one. More on that below.

The Tour

Prompt Studio

Prompt Studio is the VS Codium face of Prompt Workbench, the platform I covered in the last post. And I mean that literally, not as a marketing simplification: the extension's build scripts run nx build prompt-workbench and nx build prompt-workbench-api, then copy the resulting Angular bundle and NestJS bundle straight into the extension's webview/ and sidecar/ folders. There is no second implementation to keep in sync — when I fix a bug or ship a feature in Prompt Workbench, Prompt Studio picks it up on the next build. On activation, the extension spawns the sidecar as a child process and talks to it over plain HTTP, the same way the SaaS app's Angular front end talks to its NestJS back end. The webview is just that same front end, rendered inside a VS Codium panel instead of a browser tab.

Infographic Studio

Infographic Studio generates publication-quality SVG diagrams from structured input, and I want to be honest about where it actually stands rather than describe it as further along than it is: the generator-worker implementation — separate workers for raw SVG, Mermaid, draw.io XML, and Syncfusion output — is real, working code, but it's still on its own feature branch, not yet merged to main. I'm currently on the generator-implementations phase, chasing down a draw.io WebView rendering defect before I call that phase done. I'd rather tell you it's in progress than round up.

Compliance Studio

Compliance Studio wraps a CLI I built for architecture and standards review — seven review agents (architecture, standards, ATDD, security, UX, observability, process) that run against a live Nx project graph, in --mode pr or --mode full. The extension doesn't reimplement that review logic; it discovers the local LM Host's port through VS Code's extensionDependencies mechanism, spawns the CLI as a child process with COMPLIANCE_PROVIDER=lm-host, and streams results back line by line as the CLI emits them — each finding shows up in a tree view the moment the relevant agent finishes, not all at once when the whole run completes. Results get written to a local PouchDB audit log, and the tree view subscribes to that database's live changes feed to stay current. It also registers itself as a chat participant, so you can ask it questions about a finding directly in the shared chat sidebar instead of only reading a report.

Chat Panel

Chat Panel is the shared AI sidebar that any studio can plug into, and the contract for doing that is intentionally small: one method.

registerParticipant(
	id: string,
	displayName: string,
	handler: (request: ChatParticipantRequest, stream: ChatResponseStream) => Promise<void>,
): vscode.Disposable

A studio calls extensions.getExtension('singularity.chat-panel').exports.registerParticipant(...) during its own activation, hands over a handler function, and from that point on its participant shows up in the same sidebar as every other studio's — Compliance Studio's participant sits next to whatever Prompt Studio or a future studio registers, all inside one panel the user never has to switch between. I like this pattern specifically because it's boring: no shared state, no event bus, just a registration call and a callback. The boring parts of a platform are usually the parts that don't break at 11pm.

The Shared Platform Layer

Underneath all four studios sit three platform extensions that do the unglamorous work of making a suite feel like one product instead of four:

  • Event Bus Host — a PouchDB-backed store for domain events with a live changes feed, so studios can react to things happening in other parts of the suite without polling.
  • LM Host — an HTTP bridge that lets CLI subprocesses (like the compliance runner above) reach vscode.lm and the Anthropic/Cerebras providers without needing their own API credentials wired in separately.
  • Runtime Routing — license-aware routing between a local sidecar process and a cloud API base URL, for products that need an online/offline licensing split.

A Correction

I said in the overview article that the offline-first mandate includes "an API router with two paths — offline calls go to the local sidecar; online, licensed use routes to the cloud API gateway and microservices instead," describing that as suite-wide. Writing this piece required actually reading runtime-routing's code, and that framing overstated where things stand today: Runtime Routing is real, working infrastructure, but right now only the Lean-Agile Studio product line declares it as an extensionDependencies entry and routes through it. None of the four studios in this article currently wire into it. The rest of the offline-first mandate — local inference via Ollama and LM Studio, the bundled sidecar per extension, UI/API code reuse with the SaaS twin — is accurate and shipping today, as Prompt Studio's build pipeline above demonstrates. The cloud-routing half of the mandate is built, proven on one product line, and not yet extended to the rest of the suite. I'd rather correct that in public than let it stand uncorrected.

The Engineering Note: Why EventBus.getInstance() Doesn't Work Across Extensions

Here's a trap I hit early and want to save you from hitting yourself, if you're building anything similar. VS Code loads each extension into its own Node.js module cache. That means a singleton pattern — EventBus.getInstance() — doesn't behave like a singleton at all once you cross an extension boundary: calling it from Compliance Studio and calling it from Chat Panel returns two different objects, in two different module caches, that have never heard of each other. A subscription registered against one is invisible to events published against the other. I burned an afternoon convinced I had a bug in my event-publishing code before I realized the actual problem was architectural, not logical — I documented the fix as ADR-045 once I understood it.

The fix isn't a smarter singleton — you can't out-clever a module boundary VS Code itself enforces. The fix is to stop treating in-process pub/sub as the transport and use something that already crosses process and module boundaries by design: PouchDB's live changes feed. Event Bus Host writes every published event into a shared PouchDB database, and any extension — regardless of which module cache it's loaded into — can open a changes({ live: true }) subscription against that same database file and see everything published by every other extension. It's a slightly odd-looking solution the first time you write it, because you're using a database as your event transport instead of an in-memory bus. But it's the one that actually works given how VS Code isolates extensions from each other, and once you know the constraint, the workaround stops looking odd and starts looking obvious.

Who Else Is Building This Way?

I'm curious how common this pattern actually is outside my own repo: building a product suite as a set of cooperating VS Code / VS Codium extensions instead of shipping each idea as its own hosted web app. It's not the default choice — most tooling advice still assumes "SaaS app" is the shape a new product idea takes by default — but for a solo builder, an editor extension gets you distribution, an existing UI shell, and a captive, technical audience for free. If you've built something similar, or if you've hit the module-cache trap above from the other direction, I'd genuinely like to compare notes.

Next in this series: LeanAgileScript, the Domain-Specific Language that started the developer tooling thread — and why acceptance criteria that are readable by a product owner and directly executable as a test turned out to need a real grammar and language server, not a lighter-weight parsing trick.

← All articles