Skip to content

Conversation

@TkDodo
Copy link
Collaborator

@TkDodo TkDodo commented Nov 5, 2025

This is a continuation of

which got reverted because it broke _admin.

This version has the following changes:

  • We now add a ScrapsProvider around the root routes of _admin: 0f0615a
  • We now default to a RouterLink even if no Provider is found: 803054e
    • Falling back to an a tag is a bad idea because it will render <a to=“...”> which won’t work (this was the _admin issue). It turns out, this is actually how disabled is implemented for link, as will create links that can’t be clicked 🥲. I don’t think we need to decouple scraps from react-router right now; previously, we called useLocation, which would fail at runtime if there is no router provider, so we can’t use our Link component outside of a router context anyway.

So now, we have a ScrapsProvider around all routes (I hope there aren’t any more places) and for cases where we have no Provider, we should still render a link from react-router.

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Nov 5, 2025
@TkDodo
Copy link
Collaborator Author

TkDodo commented Nov 5, 2025

@JonasBa @evanpurkhiser I found two more places where we render a RouterProvider:

  • SimpleRouter in static/app/bootstrap/processInitQueues.tsx:

function SimpleRouter({element}: SimpleRouterProps) {
const [router] = useState(() => createBrowserRouter([{path: '*', element}]));
return <RouterProvider router={router} />;
}

  • another one in static/app/views/integrationPipeline/pipelineView.tsx

function buildRouter(Component: React.ComponentType, props: any) {
const sentryCreateBrowserRouter = wrapCreateBrowserRouterV6(createBrowserRouter);
const router = sentryCreateBrowserRouter([
{
path: '*',
element: <Component {...props} props={props} />,
},
]);
DANGEROUS_SET_REACT_ROUTER_6_HISTORY(router);
return router;
}

Since those two places use scraps components too, I think we should also wrap them in <ScrapsProviders>. The more sentry specific code we move out of scraps, the more chance for regressions we have when these places use those components.

On the other hand, we should design scraps so that it “works” without a Provider around, it just isn’t augmented with sentry specific logic (like button tracking or the link transformation). I don’t know how relevant these things are in those two places, and I also wouldn’t know how to test them ...

@TkDodo TkDodo marked this pull request as ready for review November 5, 2025 08:35
@TkDodo TkDodo requested a review from a team as a code owner November 5, 2025 08:35
Comment on lines +19 to +26
behavior: ({to, ...props}: LinkProps) => {
const normalizedTo = locationDescriptorToTo(normalizeUrl(to, location));

return {
to: normalizedTo,
...props,
};
},
Copy link

Choose a reason for hiding this comment

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

Bug: SentryLinkBehaviorProvider uses an external context, but useLinkBehavior() reads from a local context, bypassing custom link behavior.
Severity: CRITICAL | Confidence: 1.00

🔍 Detailed Analysis

The SentryLinkBehaviorProvider in static/app/scrapsProviders/link.tsx attempts to provide custom link behavior using an external LinkBehaviorContextProvider from @sentry/scraps. However, the useLinkBehavior() hook in static/app/components/core/link/link.tsx reads from a different, local LinkBehaviorContext. This mismatch prevents the custom behavior function, which includes URL normalization via normalizeUrl() and locationDescriptorToTo(), from ever being applied. Consequently, links will not have their URLs normalized as intended.

💡 Suggested Fix

Either modify useLinkBehavior() to read from the external @sentry/scraps context, or update SentryLinkBehaviorProvider to set values on the local context.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: static/app/scrapsProviders/link.tsx#L19-L26

Potential issue: The `SentryLinkBehaviorProvider` in
`static/app/scrapsProviders/link.tsx` attempts to provide custom link behavior using an
external `LinkBehaviorContextProvider` from `@sentry/scraps`. However, the
`useLinkBehavior()` hook in `static/app/components/core/link/link.tsx` reads from a
*different*, local `LinkBehaviorContext`. This mismatch prevents the custom `behavior`
function, which includes URL normalization via `normalizeUrl()` and
`locationDescriptorToTo()`, from ever being applied. Consequently, links will not have
their URLs normalized as intended.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@sentry/scraps is an alias for static/app/components/core/


export function ScrapsTestingProviders({children}: {children: React.ReactNode}) {
return <SentryLinkBehaviorProvider>{children}</SentryLinkBehaviorProvider>;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Inconsistent Sentry Wrapping Between Test and Production

The test version of ScrapsTestingProviders is missing the SentryTrackingProvider wrapper, which is present in the production ScrapsProviders component. This inconsistency means tests won't include tracking context that production code has, potentially missing bugs related to tracking functionality. The production version wraps children with both SentryTrackingProvider and SentryLinkBehaviorProvider, but the test version only includes SentryLinkBehaviorProvider.

Fix in Cursor Fix in Web

@TkDodo TkDodo merged commit 3f3093f into master Nov 6, 2025
48 checks passed
@TkDodo TkDodo deleted the tkdodo/ref/link-behaviour-context branch November 6, 2025 07:55
@sentry
Copy link

sentry bot commented Nov 6, 2025

Issues attributed to commits in this pull request

This pull request was merged and Sentry observed the following issues:

Ahmed-Labs pushed a commit that referenced this pull request Nov 6, 2025
This is a continuation of

- #102644

which got reverted because it broke `_admin`.

This version has the following changes:

- We now add a `ScrapsProvider` around the root routes of `_admin`:
0f0615a
- We now default to a `RouterLink` even if no Provider is found:
803054e
- Falling back to an `a` tag is a bad idea because it will render `<a
to=“...”>` which won’t work (this was the `_admin` issue). It turns out,
this is actually how `disabled` is implemented for link, as will create
links that can’t be clicked 🥲. I don’t think we need to decouple scraps
from react-router right now; previously, we called `useLocation`, which
would fail at runtime if there is no router provider, so we can’t use
our `Link` component outside of a router context anyway.

So now, we have a `ScrapsProvider` around all routes (I hope there
aren’t any more places) and for cases where we have no Provider, we
should still render a link from react-router.
Jesse-Box pushed a commit that referenced this pull request Nov 12, 2025
This is a continuation of

- #102644

which got reverted because it broke `_admin`.

This version has the following changes:

- We now add a `ScrapsProvider` around the root routes of `_admin`:
0f0615a
- We now default to a `RouterLink` even if no Provider is found:
803054e
- Falling back to an `a` tag is a bad idea because it will render `<a
to=“...”>` which won’t work (this was the `_admin` issue). It turns out,
this is actually how `disabled` is implemented for link, as will create
links that can’t be clicked 🥲. I don’t think we need to decouple scraps
from react-router right now; previously, we called `useLocation`, which
would fail at runtime if there is no router provider, so we can’t use
our `Link` component outside of a router context anyway.

So now, we have a `ScrapsProvider` around all routes (I hope there
aren’t any more places) and for cases where we have no Provider, we
should still render a link from react-router.
andrewshie-sentry pushed a commit that referenced this pull request Nov 13, 2025
This is a continuation of

- #102644

which got reverted because it broke `_admin`.

This version has the following changes:

- We now add a `ScrapsProvider` around the root routes of `_admin`:
0f0615a
- We now default to a `RouterLink` even if no Provider is found:
803054e
- Falling back to an `a` tag is a bad idea because it will render `<a
to=“...”>` which won’t work (this was the `_admin` issue). It turns out,
this is actually how `disabled` is implemented for link, as will create
links that can’t be clicked 🥲. I don’t think we need to decouple scraps
from react-router right now; previously, we called `useLocation`, which
would fail at runtime if there is no router provider, so we can’t use
our `Link` component outside of a router context anyway.

So now, we have a `ScrapsProvider` around all routes (I hope there
aren’t any more places) and for cases where we have no Provider, we
should still render a link from react-router.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants