Skip to content
This repository has been archived by the owner on Jan 1, 2025. It is now read-only.

Support context bridging with opaque store #298

Closed
wants to merge 9 commits into from

Conversation

inlet
Copy link
Contributor

@inlet inlet commented Jun 10, 2020

Export RecoilRoot's AppContext to make Context bridging possible.

Recoil uses React.Context internally, however the context doesn't travel through custom renderers and a Context Bridge is mostly used to solve this problem.

To make context bridging possible the AppContext needs to be reachable.

Here's an example of a Context bridge I use for ReactPixi:

import { Stage, Text } from '@inlet/react-pixi';
import { RecoilContext, useSetRecoilValue } from 'recoil';
import { theme } from './atoms';

const ContextBridge = ({ children, Context, render }) => {
  return (
    <Context.Consumer>
      {(value) => render(<Context.Provider value={value}>{children}</Context.Provider>)}
    </Context.Consumer>
  );
};

const RendererComponent = () => {
  const theme = useSetRecoilValue(theme);
  return <Text text={theme.settings} />
}

const App = () => (
    <ContextBridge
      Context={RecoilContext}
      render={(children) => <Stage>{children}</Stage>}
    >
      <RendererComponent />
    </ContextBridge>
);

Fixes #140

@facebook-github-bot
Copy link
Contributor

Hi @inlet!

Thank you for your pull request and welcome to our community.We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file.

In order for us to review and merge your code, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks!

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 10, 2020
@facebook-github-bot
Copy link
Contributor

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

1 similar comment
@facebook-github-bot
Copy link
Contributor

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@drarmstr drarmstr self-requested a review June 10, 2020 22:49
@drarmstr
Copy link
Contributor

@inlet - We don't want to expose the internal store as part of the API as it may change. It also contains React component subscriptions that wouldn't be appropriate outside the context of a <ReactRoot>. I wonder if the new Snapshot API might provide for what you are looking for. What are you trying to accomplish?

@inlet
Copy link
Contributor Author

inlet commented Jun 11, 2020

What are you trying to accomplish?

Ideally Contexts are shared between renderers, but unfortunately they're not. See #140

Custom renderers (like React ART, React Threejs Fiber, React Pixi, etc) are running an isolated reconciler. Often they are initiated from ReactDOM, but the problem is that the parent Context isn't propagated to the children in these custom renderers.

For instance:

// Stage initiates a custom renderer
// Text is a component rendered in this custom renderer
import { Stage, Text } from '@inlet/react-pixi';

import { RecoilRoot, useRecoilValue } from 'recoil';
import { theme } from './atoms';

const PixiText = () => {
  // this component is rendered in a custom renderer

  //!!! theme cannot be set as it cannot reach the recoil context
  const theme = useRecoilValue(theme);

  return <Text text={theme.color} />
}

const Drawing = () => {
  // theme is set here, as it can reach the context
  const theme = useRecoilValue(theme);

  // return a custom renderer
  // in this case react pixi
  return (
    <Stage>
      <PixiText />
    </Stage>
  );
}

const App = () => (
  <RecoilRoot>
    <Drawing />
  </RecoilRoot>
);

export default App;

I wonder if the new Snapshot API might provide for what you are looking for.

I'm not familiar with the Snapshot API, is there any docs about this? Does it serialize data? this can have a huge performance impact only for passing through context.

Looking forward to your reply, thanks!

@drarmstr
Copy link
Contributor

@inlet - It hasn't been published yet, but we're getting close. I hope to understand the requirements for your use-case to see if they might be able to be covered as well. Snapshots do not require serialization.

It might work to insert a nested controlled <RecoilRoot> in the components with the custom renderer, such as in the <Stage> or <PikiText>, where we could pass in a "snapshot" of the Recoil state that was obtained from the app. But, scalability would be a problem as it would re-render for any state change, which would defeat the purpose.. So, perhaps this won't help.

@inlet
Copy link
Contributor Author

inlet commented Jun 11, 2020

Thanks for your fast reply!

If it rerenders on every state change then I think this isn't a really good solution as it does impact the performance.

The underlying problem is React not passing through the context or at least have a way to pass it through.

Because of this limitation every custom renderer uses a Context bridge to pass through the context to the renderer. That's why exposing the actual context isn't such a bad idea.

Anyway I'm looking forwards to a decent implementation as I love Recoil's approach of state management.

Thanks!

@drarmstr
Copy link
Contributor

drarmstr commented Jun 11, 2020 via email

@inlet
Copy link
Contributor Author

inlet commented Jun 11, 2020

Ok, thanks for explaining. Give us a chance to discuss this more

Welcome, and absolutely! Please reach out if I can be of any help. Thanks

@drarmstr drarmstr added the enhancement New feature or request label Jun 11, 2020
@inlet inlet force-pushed the master branch 2 times, most recently from 67734cc to ad4d594 Compare June 19, 2020 07:42
@drarmstr
Copy link
Contributor

We really don't want to expose the internals of the Recoil store in the API. But, would the following work to support context bridging and would you be willing to update the PR?

Add a hook or something like useRecoilContext_UNSTABLE() that would return an opaque type RecoilContext that would contain the store ref. Then, add a prop to <RecoilRoot> for nested roots to optionally accept this context to use for bridging, instead of making a new nested store. Would that work for this use-case?

@inlet
Copy link
Contributor Author

inlet commented Jul 29, 2020

Sure I can update the PR. Exporting a hook useRecoilContext_UNSTABLE sound like a possible solution.

that would contain the store ref. Then, add a prop to for nested roots to optionally accept this context to use for bridging, instead of making a new nested store.

Why the need of adding a prop on <RecoilRoot>? If we've access to the actual AppContext there's no need to pass it to <RecoilRoot> right?

Here's the implementation of a <ContextBridge> I use right now:

// the context bridge
export function ContextBridge({ children, Context, render }) {
  return (
    <Context.Consumer>
      {(value) => render(
        <Context.Provider value={value}>{children}</Context.Provider>
      )}
    </Context.Consumer>
  );
};

// usage
export function PixiStage({ children, ...props }) {
   const RecoilContext = useRecoilContext();

    return (
      <ContextBridge 
        Context={RecoilContext} 
        render={children => (<Stage {...props}>{children}</Stage>)}
      >
        {children}
      </ContextBridge>
    );
  }
);

@inlet
Copy link
Contributor Author

inlet commented Jul 29, 2020

Or do you mean that useRecoilContext_UNSTABLE returns the store ref instead of the actual AppContext? Maybe name it something likeuseRecoilStoreRef_UNSTABLE and pass it to child RecoilRoot, I.e <RecoilRoot storeRef={storeRef} />

Please let me know what your thoughts are, thanks!

@drarmstr
Copy link
Contributor

Or do you mean that useRecoilContext_UNSTABLE returns the store ref instead of the actual AppContext? Maybe name it something likeuseRecoilStoreRef_UNSTABLE and pass it to child RecoilRoot, I.e <RecoilRoot storeRef={storeRef} />

Please let me know what your thoughts are, thanks!

The idea is that whatever this useRecoilContext_UNSTABLE(), or yeah probably better a useRecoilStore_UNSTABLE(), hook returns is an opaque Flow/TypeScript type. That prevents the user from depending on internal details for now, but it also means they can't just use it in a <ContextBridge> like you have above. But, if we exposed an optional store prop that accepts the opaque type, then it could allow the nested <RecoilRoot> to still use the passed in context for bridging.

@inlet inlet force-pushed the master branch 2 times, most recently from 51d96e1 to a586175 Compare July 30, 2020 08:01
@inlet
Copy link
Contributor Author

inlet commented Jul 30, 2020

I've updated the PR and it now works with a store which you can pass down to <RecoilRoot> like this:

import { atom, RecoilRoot, useRecoilStore_UNSTABLE } from 'recoil';
import { Stage, Text } from '@inlet/react-pixi';

const counterState = atom({
  key: 'counter',
  default: 0,
});

const PixiText = () => {
  // runs in custom renderer!
  const value = Recoil.useRecoilValue(counterState);

  return <ReactPixi.Text text={value} />;
};

const PixiStage = () => {
  // fetch the parent RecoilRoot store
  const store = useRecoilStore_UNSTABLE();

  // and pass it down to the next RecoilRoot
  return (
    <Stage>
      <RecoilRoot store={store}>
        <PixiText />
      </RecoilRoot>
    </Stage>
  );
};

ScreenFlow

The white box is a canvas element and runs in the custom renderer 🙌

@inlet
Copy link
Contributor Author

inlet commented Jul 30, 2020

FYI the tests are failing on the master branch.. not related to this PR.

Copy link
Contributor

@drarmstr drarmstr left a comment

Choose a reason for hiding this comment

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

Please also add unit tests in /src/hooks/__tests__

@@ -688,6 +688,10 @@ function useRecoilCallback<Args: $ReadOnlyArray<mixed>, Return>(
);
}

function useRecoilStore() {
Copy link
Contributor

Choose a reason for hiding this comment

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

The key is that this returns an opaque type. So, we need to define a new Flow type that this returns that is defined to be "opaque" (and uses an equivalent technique for the TypeScript type to be opaque)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you elaborate on the “opaque” type? This function now returns the actual Store type, which is desired right? Can you create a suggestion?

Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -103,6 +104,7 @@ module.exports = {
useRecoilTransactionObserver_UNSTABLE: useRecoilTransactionObserver,
useTransactionObservation_UNSTABLE: useTransactionObservation_DEPRECATED,
useSetUnvalidatedAtomValues_UNSTABLE: useSetUnvalidatedAtomValues,
useRecoilStore_UNSTABLE: useRecoilStore,
Copy link
Contributor

Choose a reason for hiding this comment

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

Also need to update the TypeScript definitions.

@drarmstr
Copy link
Contributor

FYI the tests are failing on the master branch.. not related to this PR.

Thanks for updating the PR! Yeah, there should be one known failure that will be resolved with some changes we have in the pipeline.. sorry about that..

@@ -42,6 +42,7 @@ type Props = {
setUnvalidatedAtomValues: (Map<string, mixed>) => void,
}) => void,
initializeState?: MutableSnapshot => void,
store?: Store,
Copy link
Contributor

Choose a reason for hiding this comment

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

Also label this as store_UNSTABLE

@inlet
Copy link
Contributor Author

inlet commented Jul 31, 2020

Can you please collaborate on this PR as you clearly have a better understanding what you want. Thanks!

@drarmstr
Copy link
Contributor

Can you please collaborate on this PR as you clearly have a better understanding what you want. Thanks!

Sure, I can update the types easily enough after we export the PR, but please add a unit test.

@inlet
Copy link
Contributor Author

inlet commented Aug 3, 2020

I've added unit tests, if you could add the types that would be 👌

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

@drarmstr has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@drarmstr drarmstr changed the title Expose the RecoilContext Support context bridging Aug 4, 2020
@drarmstr drarmstr self-assigned this Aug 4, 2020
drarmstr pushed a commit to drarmstr/Recoil that referenced this pull request Aug 4, 2020
Summary:
Export RecoilRoot's store via an opaque type to make Context bridging possible.

Recoil uses `React.Context` internally, however the context doesn't travel through custom renderers and a Context Bridge is mostly used to solve this problem.

Here's an example of a Context bridge I use for ReactPixi:

```
import { Stage, Text } from 'inlet/react-pixi';
import { RecoilContext, useSetRecoilValue } from 'recoil';
import { theme } from './atoms';

const ContextBridge = ({ children, Context, render }) => {
  return (
    <Context.Consumer>
      {(value) => render(<Context.Provider value={value}>{children}</Context.Provider>)}
    </Context.Consumer>
  );
};

const RendererComponent = () => {
  const theme = useSetRecoilValue(theme);
  return <Text text={theme.settings} />
}

const App = () => (
    <ContextBridge
      Context={RecoilContext}
      render={(children) => <Stage>{children}</Stage>}
    >
      <RendererComponent />
    </ContextBridge>
);
```

Proposed API for Recoil to support context briding without exposing the internals of the Recoil store:

```
function RenderingBridge({children}) {
  const store = useRecoilStore_UNSTABLE();
  return (
    <CustomRenderer>
      <RecoilRoot store_UNSTABLE={store}>{children}</RecoilRoot>
    </CustomRenderer>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      ...
      <RenderingBridge>
        ...
      </RenderingBridge>
    </RecoilRoot>
  );
}
```

Fixes facebookexperimental#140

Pull Request resolved: facebookexperimental#298

Differential Revision: D22929302

Pulled By: drarmstr

fbshipit-source-id: c54d2b2fd12eecca97385428d9cd449525d71632
@drarmstr
Copy link
Contributor

drarmstr commented Aug 4, 2020

@inlet -

I imported this PR to our repository and made the following changes:

  • Added opaque types for the store for Flow and TypeScript
    • I added tests for the TypeScript interface, but didn't actually test them yet.
  • Moved the hook to its own file
  • Renamed the store prop in <RecoilRoot> to store_UNSTABLE
  • For the <RecoilRoot> test:
    • It was stringifying the store interface, so it wasn't actually testing the store was the same or the same state; fix that
    • Moved <GetStore> out of testing utils since it's only used by RecoilRoot tests.
  • For the useRecoilStore() test:
    • The test currently tests that the initialized state is inherited. Also add an action to change the atom state to confirm that it is reflected in both recoil roots. However, this fails.
  • Updated SimpleReconciler to destructure React imports and removed or annotated unused params and exports.
  • Misc fixes
    • Add files headers and copyright notices
    • Remove unused dependencies and order imports
    • Add Flow type annotations for exports

However, this diff is having a conflict with the new support for concurrent mode. That diff checks if React's useMutableSource() is available and uses it. The version of React we're using internally is the experimental version with Concurrent Mode support that has this hook. However, the react-reconciler that you use in the test for context bridging specifically uses the stable release of React that is missing this hook. So, it fails when trying to use the missing hook in the nested React. I temporarily disabled this for now, but we have to figure out how to better handle this situation.

Unfortunately, with bypassing useMutableSource() the context bridging test fails. It correctly inherits the initialized state, but when the atom is set to a new value only the outer <RecoilRoot> re-renders with the change.

I also put up a diff (PR #512) with an alternative API that might be cleaner for supporting context bridging without exposing internals. We'll discuss that more amongst the team. But, it also suffers from the same problem of the failing test.

I'm not quite sure how to export my changes to this PR back to GitHub, but you can see them if you look at the individual commits for #512. @inlet, can you figure out why the context bridging test is failing when the atom is set?

@drarmstr drarmstr changed the title Support context bridging Support context bridging with opaque store Aug 6, 2020
drarmstr pushed a commit to drarmstr/Recoil that referenced this pull request Aug 6, 2020
Summary:
Export RecoilRoot's store via an opaque type to make Context bridging possible.

Recoil uses `React.Context` internally, however the context doesn't travel through custom renderers and a Context Bridge is mostly used to solve this problem.

Here's an example of a Context bridge I use for ReactPixi:

```
import { Stage, Text } from 'inlet/react-pixi';
import { RecoilContext, useSetRecoilValue } from 'recoil';
import { theme } from './atoms';

const ContextBridge = ({ children, Context, render }) => {
  return (
    <Context.Consumer>
      {(value) => render(<Context.Provider value={value}>{children}</Context.Provider>)}
    </Context.Consumer>
  );
};

const RendererComponent = () => {
  const theme = useSetRecoilValue(theme);
  return <Text text={theme.settings} />
}

const App = () => (
    <ContextBridge
      Context={RecoilContext}
      render={(children) => <Stage>{children}</Stage>}
    >
      <RendererComponent />
    </ContextBridge>
);
```

Proposed API for Recoil to support context briding without exposing the internals of the Recoil store:

```
function RenderingBridge({children}) {
  const store = useRecoilStore_UNSTABLE();
  return (
    <CustomRenderer>
      <RecoilRoot store_UNSTABLE={store}>{children}</RecoilRoot>
    </CustomRenderer>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      ...
      <RenderingBridge>
        ...
      </RenderingBridge>
    </RecoilRoot>
  );
}
```

Fixes facebookexperimental#140

Pull Request resolved: facebookexperimental#298

Differential Revision: D22929302

Pulled By: drarmstr

fbshipit-source-id: e6cbb1c1f6cf3f2bf92a070719a2b71b504d17ea
@facebook-github-bot
Copy link
Contributor

@inlet has updated the pull request. You must reimport the pull request before landing.

@drarmstr
Copy link
Contributor

drarmstr commented Aug 6, 2020

@inlet - Ok, I was able to update your branch with the changes so you can see it easier and hopefully reproduce and fix the failing test.

I also put up another API proposal for context bridging on top of this for us to evaluate with #516 as an alternative to #512

@inlet
Copy link
Contributor Author

inlet commented Aug 7, 2020

@drarmstr Thanks for all the effort!

First of all, I'd love to collaborate on this with you to work out a solid solution, however I'm very limited in time and won't be able to work on this on short term.

Luckily the fix is almost there.. given that you have a better mental model of this codebase (and the new React concurrent mode) + you are the core contributor of the project makes you more suitable to solve the last piece of the puzzle 😄

Thanks!

@drarmstr
Copy link
Contributor

@inlet I don't have the bandwidth to explore with an actual nested renderer using react-reconciler right now. I simplified the test to just use a nested ReactDOM.render() and it sidesteps the compatible React version issue with useMutableSource(), and also actually fixed the test propagating changes for both React roots. So, We can proceed with this and hopefully you can validate that this works for your context bridging in the field with master once it lands.

We're also currently thinking of using #516 for the API

@facebook-github-bot
Copy link
Contributor

@drarmstr merged this pull request in 2b1cd3a.

AlexGuz23 pushed a commit to AlexGuz23/Recoil that referenced this pull request Nov 3, 2022
Summary:
NOTE: Please note updated context bridging proposal in D22983479 or D22935480

Export RecoilRoot's store via an opaque type to make Context bridging possible.

Recoil uses `React.Context` internally, however the context doesn't travel through custom renderers and a Context Bridge is mostly used to solve this problem.

Here's an example of a Context bridge I use for ReactPixi:

```
import { Stage, Text } from 'inlet/react-pixi';
import { RecoilContext, useSetRecoilValue } from 'recoil';
import { theme } from './atoms';

const ContextBridge = ({ children, Context, render }) => {
  return (
    <Context.Consumer>
      {(value) => render(<Context.Provider value={value}>{children}</Context.Provider>)}
    </Context.Consumer>
  );
};

const RendererComponent = () => {
  const theme = useSetRecoilValue(theme);
  return <Text text={theme.settings} />
}

const App = () => (
    <ContextBridge
      Context={RecoilContext}
      render={(children) => <Stage>{children}</Stage>}
    >
      <RendererComponent />
    </ContextBridge>
);
```

Proposed API for Recoil to support context briding without exposing the internals of the Recoil store:

```
function RenderingBridge({children}) {
  const store = useRecoilStore_UNSTABLE();
  return (
    <CustomRenderer>
      <RecoilRoot store_UNSTABLE={store}>{children}</RecoilRoot>
    </CustomRenderer>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      ...
      <RenderingBridge>
        ...
      </RenderingBridge>
    </RecoilRoot>
  );
}
```

Fixes facebookexperimental/Recoil#140

Pull Request resolved: facebookexperimental/Recoil#298

Reviewed By: davidmccabe

Differential Revision: D22929302

Pulled By: drarmstr

fbshipit-source-id: 41c6d8d66d9326da0e1d09583d98934f60ab6ba2
snipershooter0701 pushed a commit to snipershooter0701/Recoil that referenced this pull request Mar 5, 2023
Summary:
NOTE: Please note updated context bridging proposal in D22983479 or D22935480

Export RecoilRoot's store via an opaque type to make Context bridging possible.

Recoil uses `React.Context` internally, however the context doesn't travel through custom renderers and a Context Bridge is mostly used to solve this problem.

Here's an example of a Context bridge I use for ReactPixi:

```
import { Stage, Text } from 'inlet/react-pixi';
import { RecoilContext, useSetRecoilValue } from 'recoil';
import { theme } from './atoms';

const ContextBridge = ({ children, Context, render }) => {
  return (
    <Context.Consumer>
      {(value) => render(<Context.Provider value={value}>{children}</Context.Provider>)}
    </Context.Consumer>
  );
};

const RendererComponent = () => {
  const theme = useSetRecoilValue(theme);
  return <Text text={theme.settings} />
}

const App = () => (
    <ContextBridge
      Context={RecoilContext}
      render={(children) => <Stage>{children}</Stage>}
    >
      <RendererComponent />
    </ContextBridge>
);
```

Proposed API for Recoil to support context briding without exposing the internals of the Recoil store:

```
function RenderingBridge({children}) {
  const store = useRecoilStore_UNSTABLE();
  return (
    <CustomRenderer>
      <RecoilRoot store_UNSTABLE={store}>{children}</RecoilRoot>
    </CustomRenderer>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      ...
      <RenderingBridge>
        ...
      </RenderingBridge>
    </RecoilRoot>
  );
}
```

Fixes facebookexperimental/Recoil#140

Pull Request resolved: facebookexperimental/Recoil#298

Reviewed By: davidmccabe

Differential Revision: D22929302

Pulled By: drarmstr

fbshipit-source-id: 41c6d8d66d9326da0e1d09583d98934f60ab6ba2
eagle2722 added a commit to eagle2722/Recoil that referenced this pull request Sep 21, 2024
Summary:
NOTE: Please note updated context bridging proposal in D22983479 or D22935480

Export RecoilRoot's store via an opaque type to make Context bridging possible.

Recoil uses `React.Context` internally, however the context doesn't travel through custom renderers and a Context Bridge is mostly used to solve this problem.

Here's an example of a Context bridge I use for ReactPixi:

```
import { Stage, Text } from 'inlet/react-pixi';
import { RecoilContext, useSetRecoilValue } from 'recoil';
import { theme } from './atoms';

const ContextBridge = ({ children, Context, render }) => {
  return (
    <Context.Consumer>
      {(value) => render(<Context.Provider value={value}>{children}</Context.Provider>)}
    </Context.Consumer>
  );
};

const RendererComponent = () => {
  const theme = useSetRecoilValue(theme);
  return <Text text={theme.settings} />
}

const App = () => (
    <ContextBridge
      Context={RecoilContext}
      render={(children) => <Stage>{children}</Stage>}
    >
      <RendererComponent />
    </ContextBridge>
);
```

Proposed API for Recoil to support context briding without exposing the internals of the Recoil store:

```
function RenderingBridge({children}) {
  const store = useRecoilStore_UNSTABLE();
  return (
    <CustomRenderer>
      <RecoilRoot store_UNSTABLE={store}>{children}</RecoilRoot>
    </CustomRenderer>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      ...
      <RenderingBridge>
        ...
      </RenderingBridge>
    </RecoilRoot>
  );
}
```

Fixes facebookexperimental/Recoil#140

Pull Request resolved: facebookexperimental/Recoil#298

Reviewed By: davidmccabe

Differential Revision: D22929302

Pulled By: drarmstr

fbshipit-source-id: 41c6d8d66d9326da0e1d09583d98934f60ab6ba2
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. enhancement New feature or request Merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support "context bridging"
3 participants