Skip to content

Tech design: Runtime context refactor#8590

Draft
ericpgreen2 wants to merge 6 commits intomainfrom
runtime-context-plan
Draft

Tech design: Runtime context refactor#8590
ericpgreen2 wants to merge 6 commits intomainfrom
runtime-context-plan

Conversation

@ericpgreen2
Copy link
Contributor

@ericpgreen2 ericpgreen2 commented Jan 5, 2026

Problem: The frontend manages runtime connection info (host, instanceId, JWT) through a global mutable Svelte store. This works when there's one runtime, but breaks down when a page needs to talk to multiple runtimes simultaneously — which is the case for cloud editing, where a dev deployment coexists with production. The cloud editing MVP (PR #8912) works around this with conditional rendering and manual store overwriting, but the workarounds are fragile.

Proposal: Replace the global store with a RuntimeProvider Svelte component that creates a scoped RuntimeClient and sets it in Svelte context. Components read the client from context instead of importing a global store. Nesting providers gives multi-runtime support for free. A lightweight runtimeRegistry supplements Svelte context for .ts modules that can't receive RuntimeClient from a component ancestor (SSE clients, test harnesses). The current Orval-generated query hooks are also tied to the global store — every generated function reads the singleton httpClient — so they can't be reused as-is. Since we need new code generation anyway, we migrate the transport layer to ConnectRPC at the same time, using a custom code generator to produce TanStack Query hooks from the protobuf service definitions.

The detailed plan is in plans/runtime-context-architecture.md. It covers the RuntimeClient class design, code generator, invalidation strategy, a batched consumer migration (~100 files), and a 6-PR shipping sequence. It also responds to feedback from earlier rounds of review on this PR.

Seeking feedback — not intended to merge to main.

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

Developed in collaboration with Claude Code


Each subtree uses its own transport and instanceId. Components don't need to know which runtime they're in.

## Code Generator
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a library for this already?

});

const client = createRuntimeServiceClient(transport);
const explore = await client.getExplore({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't populate the tanstack query cache right? We might need to restructure our generated code to support something direct fetching with cache population.

Or avoid loader functions altogether.


## Open Questions

1. **Query key namespacing:** Should query keys include a version or hash to handle proto schema changes?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this give us? Wouldnt the backend APIs not exist for the previous endpoints anymore?

// Switch to viewing as another user
async function impersonateUser(userId: string) {
const newJwt = await adminService.getImpersonationToken(userId);
// Update RuntimeProvider props → new transport created → queries refetch
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this mean this function will have to sit above the <RuntimeProvider> wrap? Would be good to flesh this section out to make sure there are no issues.

@briangregoryholmes
Copy link
Contributor

briangregoryholmes commented Jan 7, 2026

One thing that this touches on, but does not fully incorporate is the dependency between runtime data and a project. I think, historically, bringing the terms host/runtime/instanceId to the forefront confused what was actually happening, which is that a user was simply querying something in a project.

To that end, I don't think we should have a RuntimeProvider, but rather a ProjectProvider, with the context being the project name and org name or even the connectrpc client itself. Currently, the tech design useRuntimeService hook retrieves the instanceId and transport and then creates the client, but this is unnecessary. The responsibility for creating the client should exist further up the chain as part of some manager class (that also handles scheduled project data refetching, etc.). There's no need to provide the transport/instanceId as context.

I also think the useRuntimeService hook should work in both .svelte and .ts contexts. That way, the necessary ids for lookup can be provided when outside of a component. As such, the methods on the custom client object should return both createQuery functions (as they do currently) and the options object (for use with queryClient.fetchQuery()). This would hugely simplify a headache we have with Orval currently.

Here's a very rough working implementation of what I'm imagining: #8603

@ericpgreen2 ericpgreen2 marked this pull request as draft January 16, 2026 14:04
Replaces the original high-level options analysis with a concrete
implementation plan covering RuntimeClient class, code generator,
invalidation updates, consumer migration strategy, and PR sequencing.
Addresses feedback from Brian and Aditya on the original doc.
The JSON bridge modifies the v2 code generator so hooks accept/return
Orval-compatible types (V1*) instead of proto types. This lets consumers
switch to v2 hooks without changing their type usage, unblocking global
store removal independent of the oneof type migration.
Restructure the plan so the JSON bridge reads as part of the code
generator's design rather than a separate phase patched on later.
Phases renumbered from 7 to 6.
…design

Integrates the lightweight runtime registry into Phase 1 as foundational
infrastructure alongside `RuntimeClient` and `RuntimeProvider`. Adds
concrete `.ts` module migration guidance to Phase 4 covering query option
factories, module-level singletons, and SSE/streaming code. Addresses
Brian's feedback on non-component access to `RuntimeClient`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants