Skip to content

Conversation

@ryan953
Copy link
Member

@ryan953 ryan953 commented Nov 7, 2025

This consolidates a bunch of stuff that was split up for video replays vs. non-video. We didn't need to split so much up into separate bits. Not that things are together it's easier to drop this component into replayView.tsx as well as replayPreviewPlayer.tsx

The flutter setup warning is really the only thing i moved around. It's on the right now with a more appropriatly sized icon:

Before After
Screenshot 2025-11-10 at 11 25 42 AM Screenshot 2025-11-10 at 11 25 31 AM

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Nov 7, 2025
@ryan953 ryan953 changed the title Ryan953/replay url compactselect feat(replay): Adding pseudo browser next/prev buttons to replay details, near the existing url bar Nov 7, 2025
@ryan953 ryan953 force-pushed the ryan953/replay-url-compactselect branch from 5009e99 to 90c9d04 Compare November 10, 2025 03:22
@ryan953 ryan953 changed the title feat(replay): Adding pseudo browser next/prev buttons to replay details, near the existing url bar ref(replay): Refactor curent url/screen components Nov 10, 2025
@ryan953 ryan953 changed the title ref(replay): Refactor curent url/screen components ref(replay): Refactor ReplayCurrentURL & CurrentScreen components Nov 10, 2025
@ryan953 ryan953 marked this pull request as ready for review November 10, 2025 03:28
@ryan953 ryan953 requested a review from a team as a code owner November 10, 2025 03:28
@codecov
Copy link

codecov bot commented Nov 10, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
12378 1 12377 10
View the top 1 failed test(s) by shortest run time
ReplayClipPreview Display URL and breadcrumbs in fullscreen mode
Stack Traces | 0.289s run time
Error: Expected test not to call console.error().

If the error is expected, test for it explicitly by mocking it out using jest.spyOn(console, 'error').mockImplementation() and test that the warning occurs.

An update to Control inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
    at console.captureMessage [as error] (.../sentry/node_modules/.pnpm/[email protected]..../node_modules/jest-fail-on-console/index.js:83:25)
    at .../sentry/node_modules/.pnpm/[email protected][email protected]..../react-dom/cjs/react-dom-client.development.js:18758:19
    at runWithFiberInDEV (.../sentry/node_modules/.pnpm/[email protected][email protected]..../react-dom/cjs/react-dom-client.development.js:874:13)
    at warnIfUpdatesNotWrappedWithActDEV (.../sentry/node_modules/.pnpm/[email protected][email protected]..../react-dom/cjs/react-dom-client.development.js:18757:9)
    at scheduleUpdateOnFiber (.../sentry/node_modules/.pnpm/[email protected][email protected]..../react-dom/cjs/react-dom-client.development.js:16409:11)
    at dispatchSetStateInternal (.../sentry/node_modules/.pnpm/[email protected][email protected]..../react-dom/cjs/react-dom-client.development.js:9170:13)
    at dispatchSetState (.../sentry/node_modules/.pnpm/[email protected][email protected]..../react-dom/cjs/react-dom-client.development.js:9127:7)
    at .../sentry/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected][email protected]..../lib/cjs/usePopper.js:64:11
    at Object.process.env.NODE_ENV.exports.flushSync (.../sentry/node_modules/.pnpm/[email protected][email protected]..../react-dom/cjs/react-dom.development.js:136:18)
    at fn (.../sentry/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected][email protected]..../lib/cjs/usePopper.js:63:18)
    at Object.fn [as forceUpdate] (.../sentry/node_modules/.pnpm/@[email protected]/node_modules/@.../core/src/createPopper.js:235:21)
    at forceUpdate (.../sentry/node_modules/.pnpm/@[email protected]/node_modules/@.../core/src/createPopper.js:245:22)
    at new Promise (<anonymous>)
    at .../sentry/node_modules/.pnpm/@[email protected]/node_modules/@.../core/src/createPopper.js:244:11
    at fn (.../sentry/node_modules/.pnpm/@[email protected]/node_modules/@.../src/utils/debounce.js:10:19)
    at processTicksAndRejections (node:internal/process/task_queues:105:5)
    at flushUnexpectedConsoleCalls (.../sentry/node_modules/.pnpm/[email protected]..../node_modules/jest-fail-on-console/index.js:48:13)
    at Object.<anonymous> (.../sentry/node_modules/.pnpm/[email protected]..../node_modules/jest-fail-on-console/index.js:145:7)
    at Promise.finally.completed (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:1559:28)
    at new Promise (<anonymous>)
    at callAsyncCircusFn (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:1499:10)
    at _callCircusHook (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:978:40)
    at processTicksAndRejections (node:internal/process/task_queues:105:5)
    at _runTest (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:951:5)
    at _runTestsForDescribeBlock (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:839:13)
    at _runTestsForDescribeBlock (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:829:11)
    at run (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:757:3)
    at runAndTransformResultsToJestFormat (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/jestAdapterInit.js:1920:21)
    at jestAdapter (.../sentry/node_modules/.pnpm/[email protected][email protected]..../jest-circus/build/runner.js:101:19)
    at runTestInternal (.../sentry/node_modules/.pnpm/[email protected]..../jest-runner/build/testWorker.js:272:16)
    at runTest (.../sentry/node_modules/.pnpm/[email protected]..../jest-runner/build/testWorker.js:340:7)
    at Object.worker (.../sentry/node_modules/.pnpm/[email protected]..../jest-runner/build/testWorker.js:494:12)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Bug: Grid Layout Misalignment After Refactor

The ContextContainer grid layout expects three columns (grid-template-columns: 1fr max-content max-content) but after the refactoring only receives two children: ReplayCurrentLocation and ReplaySidebarToggleButton. Previously it had three children: the URL/screen component, BrowserOSIcons, and the sidebar toggle. This causes incorrect grid layout where the second child occupies the second column instead of the third, leaving the third column definition unused.

static/app/components/events/eventReplay/replayPreviewPlayer.tsx#L244-L251

const ContextContainer = styled('div')`
display: grid;
grid-auto-flow: column;
grid-template-columns: 1fr max-content max-content;
align-items: center;
gap: ${space(1)};
`;

Fix in Cursor Fix in Web


<ContextContainer>
{isVideoReplay ? <ReplayCurrentScreen /> : <ReplayCurrentUrl />}
<BrowserOSIcons />
<ReplayCurrentLocation isLoading={false} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Replay Fullscreen Layout Breaks

The ReplayCurrentLocation component now needs the BrowserOSIcons and ReplayViewScale components to render properly, but these only work correctly when not in fullscreen mode. In fullscreen mode within the preview player, ReplayCurrentLocation renders these components, but they were previously rendered standalone. The ContextContainer grid layout (with grid-template-columns: 1fr max-content max-content) expects three children but ReplayCurrentLocation returns a Flex with potentially different alignment, breaking the expected layout when fullscreen.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

they render fine

@ryan953
Copy link
Member Author

ryan953 commented Nov 10, 2025

@sentry review

import * as Sentry from '@sentry/react';

import {Flex} from '@sentry/scraps/layout/flex';
import {ExternalLink, Link} from '@sentry/scraps/link';
Copy link

Choose a reason for hiding this comment

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

Bug: Incorrect import path for Link component, causing a module import failure.
Severity: CRITICAL | Confidence: 1.00

🔍 Detailed Analysis

The Link component is incorrectly imported from '@sentry/scraps/link' instead of '@sentry/scraps/link/link'. This will lead to a module import failure at runtime because Link is not exported from the former path, preventing the replayCurrentLocationInput.tsx component from rendering and breaking the replay player view.

💡 Suggested Fix

Update the import statements to import {ExternalLink} from '@sentry/scraps/link'; and import {Link} from '@sentry/scraps/link/link'; to correctly resolve the Link component.

🤖 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/components/replays/replayCurrentLocationInput.tsx#L6

Potential issue: The `Link` component is incorrectly imported from
`'@sentry/scraps/link'` instead of `'@sentry/scraps/link/link'`. This will lead to a
module import failure at runtime because `Link` is not exported from the former path,
preventing the `replayCurrentLocationInput.tsx` component from rendering and breaking
the replay player view.

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

Comment on lines 68 to 72
<Flex gap="sm" flex="1" align="center">
<Tooltip title={scrubbingTooltip} disabled={!scrubbingTooltip} skipWrapper>
<FlexTextCopyInput aria-label={t('Copy to clipboard')} size="sm">
{currentLocation}
</FlexTextCopyInput>
Copy link

Choose a reason for hiding this comment

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

The aria-label for the TextCopyInput is set to t('Copy to clipboard'), which is generic and doesn't describe the actual content (e.g., 'Current URL' or 'Current Screen Name'). For better accessibility, especially for screen readers, consider making the aria-label more descriptive of what URL/location is being displayed (e.g., 'Current replay location'). This would help users understand what information they're about to copy.
Severity: MEDIUM

🤖 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/components/replays/replayCurrentLocationInput.tsx#L68-L72

Potential issue: The `aria-label` for the TextCopyInput is set to `t('Copy to
clipboard')`, which is generic and doesn't describe the actual content (e.g., 'Current
URL' or 'Current Screen Name'). For better accessibility, especially for screen readers,
consider making the aria-label more descriptive of what URL/location is being displayed
(e.g., 'Current replay location'). This would help users understand what information
they're about to copy.

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

Comment on lines +22 to +27
<ErrorBoundary customComponent={FatalIconTooltip}>
<BrowserOSIcons showBrowser={!isVideoReplay} isLoading={isLoading} />
</ErrorBoundary>
<ErrorBoundary customComponent={FatalIconTooltip}>
<ReplayViewScale isLoading={isLoading} />
</ErrorBoundary>
Copy link

Choose a reason for hiding this comment

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

Both BrowserOSIcons and ReplayViewScale are wrapped with ErrorBoundary using the FatalIconTooltip error component. While this handles errors gracefully, ensure that if an error occurs in either component, the user still has a functional replay player. The FatalIconTooltip displays only an icon, which may not be obvious to users unfamiliar with the Sentry UI. Consider logging to Sentry (via ErrorBoundary integration) to ensure these errors are tracked.
Severity: MEDIUM

🤖 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/components/replays/replayCurrentLocation.tsx#L22-L27

Potential issue: Both `BrowserOSIcons` and `ReplayViewScale` are wrapped with
`ErrorBoundary` using the `FatalIconTooltip` error component. While this handles errors
gracefully, ensure that if an error occurs in either component, the user still has a
functional replay player. The `FatalIconTooltip` displays only an icon, which may not be
obvious to users unfamiliar with the Sentry UI. Consider logging to Sentry (via
`ErrorBoundary` integration) to ensure these errors are tracked.

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

Comment on lines 41 to +51
<PlayerBreadcrumbContainer>
<PlayerContainer>
<ContextContainer>
{isLoading ? (
<TextCopyInput size="sm" disabled>
{''}
</TextCopyInput>
) : isVideoReplay ? (
<ScreenNameContainer>
{replay?.getReplay()?.sdk.name?.includes('flutter') ? (
<QuestionTooltip
isHoverable
title={tct(
'In order to see the correct screen name, you need to configure the [link:Sentry Routing Instrumentation].',
{
link: (
<ExternalLink href="https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation/" />
),
}
)}
size="sm"
/>
) : null}
<ScreenNameInputContainer>
<ReplayCurrentScreen />
</ScreenNameInputContainer>
</ScreenNameContainer>
) : (
<ReplayCurrentUrl />
)}

<ErrorBoundary customComponent={FatalIconTooltip}>
<BrowserOSIcons showBrowser={!isVideoReplay} isLoading={isLoading} />
</ErrorBoundary>
<ErrorBoundary customComponent={FatalIconTooltip}>
<ReplayViewScale isLoading={isLoading} />
</ErrorBoundary>
<Flex gap="lg" flex="1">
<ReplayCurrentLocation isLoading={isLoading} />
{isFullscreen ? (
<ReplaySidebarToggleButton
isOpen={isSidebarOpen}
setIsOpen={setIsSidebarOpen}
/>
) : null}
</ContextContainer>
</Flex>
Copy link

Choose a reason for hiding this comment

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

The old ContextContainer styled div has been replaced with a Flex component using gap="lg" for spacing. However, the old ContextContainer used grid-template-columns: 1fr max-content; (which is different from the new Flex layout). Verify that the new layout produces the same visual result across different screen sizes and content widths. The Flex component with gap="lg" may behave differently when items need to wrap or when space is constrained.
Severity: MEDIUM

🤖 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/components/replays/replayView.tsx#L41-L51

Potential issue: The old `ContextContainer` styled div has been replaced with a `Flex`
component using `gap="lg"` for spacing. However, the old `ContextContainer` used
`grid-template-columns: 1fr max-content;` (which is different from the new `Flex`
layout). Verify that the new layout produces the same visual result across different
screen sizes and content widths. The `Flex` component with `gap="lg"` may behave
differently when items need to wrap or when space is constrained.

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

Comment on lines +77 to +89
}

function getCurrentLocation(replay: null | ReplayReader, currentTime: number) {
try {
return replay?.isVideoReplay()
? getCurrentScreenName(
replay?.getReplay(),
replay?.getMobileNavigationFrames(),
currentTime
)
: getCurrentUrl(replay?.getReplay(), replay?.getNavigationFrames(), currentTime);
} catch (error) {
Sentry.captureException(error);
Copy link

Choose a reason for hiding this comment

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

The function getCurrentLocation() catches all errors with catch (error) and logs them to Sentry via Sentry.captureException(error). This is good for monitoring, but returning an empty string on error may silently hide issues from the user. Consider providing user feedback (e.g., a toast notification or inline error message) when this error occurs, so users know why they're seeing an empty location field.
Severity: MEDIUM

🤖 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/components/replays/replayCurrentLocationInput.tsx#L77-L89

Potential issue: The function `getCurrentLocation()` catches all errors with `catch
(error)` and logs them to Sentry via `Sentry.captureException(error)`. This is good for
monitoring, but returning an empty string on error may silently hide issues from the
user. Consider providing user feedback (e.g., a toast notification or inline error
message) when this error occurs, so users know why they're seeing an empty location
field.

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

<Tooltip title={scrubbingTooltip} disabled={!scrubbingTooltip} skipWrapper>
<FlexTextCopyInput aria-label={t('Current Location')} size="sm">
{currentLocation}
</FlexTextCopyInput>
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Interactive Empty Fields Bug

The TextCopyInput is missing the disabled prop when currentLocation is empty. Previously, both replayCurrentUrl.tsx and replayCurrentScreen.tsx rendered a disabled input when there was no data to display, but the refactored ReplayCurrentLocationInput component always renders an enabled input, even with empty content. This allows interaction with an empty field where none should be possible.

Fix in Cursor Fix in Web

Copy link
Member

@srest2021 srest2021 left a comment

Choose a reason for hiding this comment

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

Looks good, just have some minor UI suggestions

}

const FlexTextCopyInput = styled(TextCopyInput)`
flex: 1;
Copy link
Member

Choose a reason for hiding this comment

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

Could just wrap <TextCopyInput> in <Flex flex="1"> instead. Low logaf though


return (
<Flex gap="sm" flex="1" align="center">
<Tooltip title={scrubbingTooltip} disabled={!scrubbingTooltip} skipWrapper>
Copy link
Member

Choose a reason for hiding this comment

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

We can do isHoverable for this tooltip, otherwise it disappears when I go to click the links

Suggested change
<Tooltip title={scrubbingTooltip} disabled={!scrubbingTooltip} skipWrapper>
<Tooltip title={scrubbingTooltip} disabled={!scrubbingTooltip} skipWrapper isHoverable>

<Flex gap="sm" flex="1" align="center">
<Tooltip title={scrubbingTooltip} disabled={!scrubbingTooltip} skipWrapper>
<FlexTextCopyInput aria-label={t('Current Location')} size="sm">
{currentLocation}
Copy link
Member

Choose a reason for hiding this comment

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

(Cursor bot already mentioned this but) previously we were disabling TextCopyInput when the url is empty, do we still want to do this? Would probably also be safer to always display an empty string whenever isLoading is true in replayCurrentLocation.tsx.

const isVideoReplay = replay?.isVideoReplay();

return (
<Flex align="center" flex="1" gap="sm" justify="between" radius="md">
Copy link
Member

Choose a reason for hiding this comment

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

Pretty minor but there's a small spacing difference:

Before:

Image

After:

Image

Changing gap="sm" to gap="lg" gets it back to the way it was before

Suggested change
<Flex align="center" flex="1" gap="sm" justify="between" radius="md">
<Flex align="center" flex="1" gap="lg" justify="between" radius="md">

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.

3 participants