Browse Source

ref(autofix): Move actions to document and remove message box (#83252)

(Note for issues team if you open this PR: this PR only contains changes
inside the Sentry AI section of the drawer)

I know this PR has a big change count, but it's really just one change:
removing the message box. The user's actions are now on the root cause
and fix cards, and logs and user input are in the output stream. It's a
lot of moving existing code and tests, and deleting dead code. I also
had to include some styling changes for this to all look coherent and
readable, plus tacking on some other styling changes for other existing
readability issues. Nothing has changed on functionality.

(The insight card justifications will look better once the backend
change goes in)

<img width="694" alt="Screenshot 2025-01-10 at 8 18 52 AM"
src="https://github.com/user-attachments/assets/70e01bc4-f508-45e1-880e-7f94a7bde67f"
/>
<img width="723" alt="Screenshot 2025-01-10 at 8 19 00 AM"
src="https://github.com/user-attachments/assets/a96d362d-5ac9-4e77-b60f-65c30cbd2c91"
/>
<img width="675" alt="Screenshot 2025-01-10 at 8 49 31 AM"
src="https://github.com/user-attachments/assets/423aec23-c0e5-44fd-9396-18b97d306074"
/>
<img width="662" alt="Screenshot 2025-01-10 at 11 42 49 AM"
src="https://github.com/user-attachments/assets/ac230aae-cc94-4146-aa15-ee79ce8605ce"
/>
<img width="653" alt="Screenshot 2025-01-14 at 6 50 00 AM"
src="https://github.com/user-attachments/assets/4cb3449c-6656-4663-9501-9212376af753"
/>
<img width="634" alt="Screenshot 2025-01-14 at 6 49 49 AM"
src="https://github.com/user-attachments/assets/597c94e3-4833-45ad-8b3d-559d12029161"
/>

---------

Co-authored-by: Jenn Mueng <30991498+jennmueng@users.noreply.github.com>
Rohan Agarwal 1 month ago
parent
commit
b502325fbc

+ 183 - 0
static/app/components/events/autofix/autofixChanges.analytics.spec.tsx

@@ -0,0 +1,183 @@
+import {AutofixCodebaseChangeData} from 'sentry-fixture/autofixCodebaseChangeData';
+import {AutofixStepFixture} from 'sentry-fixture/autofixStep';
+
+import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
+
+import {Button} from 'sentry/components/button';
+import {AutofixChanges} from 'sentry/components/events/autofix/autofixChanges';
+import {
+  type AutofixChangesStep,
+  AutofixStepType,
+} from 'sentry/components/events/autofix/types';
+
+jest.mock('sentry/components/button', () => ({
+  Button: jest.fn(props => {
+    // Forward the click handler while allowing us to inspect props
+    return <button onClick={props.onClick}>{props.children}</button>;
+  }),
+  LinkButton: jest.fn(props => {
+    return <a href={props.href}>{props.children}</a>;
+  }),
+}));
+
+const mockButton = Button as jest.MockedFunction<typeof Button>;
+
+describe('AutofixChanges', () => {
+  const defaultProps = {
+    groupId: '123',
+    runId: '456',
+    step: AutofixStepFixture({
+      type: AutofixStepType.CHANGES,
+      changes: [AutofixCodebaseChangeData()],
+    }) as AutofixChangesStep,
+  };
+
+  beforeEach(() => {
+    MockApiClient.clearMockResponses();
+    mockButton.mockClear();
+  });
+
+  it('passes correct analytics props for Create PR button when write access is enabled', async () => {
+    MockApiClient.addMockResponse({
+      url: '/issues/123/autofix/setup/?check_write_access=true',
+      method: 'GET',
+      body: {
+        genAIConsent: {ok: true},
+        integration: {ok: true},
+        githubWriteIntegration: {
+          repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
+        },
+      },
+    });
+
+    render(<AutofixChanges {...defaultProps} />);
+    await userEvent.click(screen.getByRole('button', {name: 'Draft PR'}));
+
+    const createPRButtonCall = mockButton.mock.calls.find(
+      call => call[0]?.analyticsEventKey === 'autofix.create_pr_clicked'
+    );
+    expect(createPRButtonCall?.[0]).toEqual(
+      expect.objectContaining({
+        analyticsEventKey: 'autofix.create_pr_clicked',
+        analyticsEventName: 'Autofix: Create PR Clicked',
+        analyticsParams: {group_id: '123'},
+      })
+    );
+  });
+
+  it('passes correct analytics props for Create PR Setup button when write access is not enabled', () => {
+    MockApiClient.addMockResponse({
+      url: '/issues/123/autofix/setup/?check_write_access=true',
+      method: 'GET',
+      body: {
+        genAIConsent: {ok: true},
+        integration: {ok: true},
+        githubWriteIntegration: {
+          repos: [{ok: false, owner: 'owner', name: 'hello-world', id: 100}],
+        },
+      },
+    });
+
+    render(<AutofixChanges {...defaultProps} />);
+
+    // Find the last call to Button that matches our Setup button
+    const setupButtonCall = mockButton.mock.calls.find(
+      call => call[0].children === 'Draft PR'
+    );
+    expect(setupButtonCall?.[0]).toEqual(
+      expect.objectContaining({
+        analyticsEventKey: 'autofix.create_pr_setup_clicked',
+        analyticsEventName: 'Autofix: Create PR Setup Clicked',
+        analyticsParams: {
+          group_id: '123',
+        },
+      })
+    );
+  });
+
+  it('passes correct analytics props for Create Branch button when write access is enabled', async () => {
+    MockApiClient.addMockResponse({
+      url: '/issues/123/autofix/setup/?check_write_access=true',
+      method: 'GET',
+      body: {
+        genAIConsent: {ok: true},
+        integration: {ok: true},
+        githubWriteIntegration: {
+          repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
+        },
+      },
+    });
+
+    render(<AutofixChanges {...defaultProps} />);
+
+    await userEvent.click(screen.getByRole('button', {name: 'Check Out Locally'}));
+
+    const createBranchButtonCall = mockButton.mock.calls.find(
+      call => call[0]?.analyticsEventKey === 'autofix.push_to_branch_clicked'
+    );
+    expect(createBranchButtonCall?.[0]).toEqual(
+      expect.objectContaining({
+        analyticsEventKey: 'autofix.push_to_branch_clicked',
+        analyticsEventName: 'Autofix: Push to Branch Clicked',
+        analyticsParams: {group_id: '123'},
+      })
+    );
+  });
+
+  it('passes correct analytics props for Create Branch Setup button when write access is not enabled', () => {
+    MockApiClient.addMockResponse({
+      url: '/issues/123/autofix/setup/?check_write_access=true',
+      method: 'GET',
+      body: {
+        genAIConsent: {ok: true},
+        integration: {ok: true},
+        githubWriteIntegration: {
+          repos: [{ok: false, owner: 'owner', name: 'hello-world', id: 100}],
+        },
+      },
+    });
+
+    render(<AutofixChanges {...defaultProps} />);
+
+    const setupButtonCall = mockButton.mock.calls.find(
+      call => call[0].children === 'Check Out Locally'
+    );
+    expect(setupButtonCall?.[0]).toEqual(
+      expect.objectContaining({
+        analyticsEventKey: 'autofix.create_branch_setup_clicked',
+        analyticsEventName: 'Autofix: Create Branch Setup Clicked',
+        analyticsParams: {
+          group_id: '123',
+        },
+      })
+    );
+  });
+
+  it('passes correct analytics props for Add Tests button', () => {
+    MockApiClient.addMockResponse({
+      url: '/issues/123/autofix/setup/?check_write_access=true',
+      method: 'GET',
+      body: {
+        genAIConsent: {ok: true},
+        integration: {ok: true},
+        githubWriteIntegration: {
+          repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
+        },
+      },
+    });
+
+    render(<AutofixChanges {...defaultProps} />);
+    screen.getByText('Add Tests').click();
+
+    const addTestsButtonCall = mockButton.mock.calls.find(
+      call => call[0]?.analyticsEventKey === 'autofix.add_tests_clicked'
+    );
+    expect(addTestsButtonCall?.[0]).toEqual(
+      expect.objectContaining({
+        analyticsEventKey: 'autofix.add_tests_clicked',
+        analyticsEventName: 'Autofix: Add Tests Clicked',
+        analyticsParams: {group_id: '123'},
+      })
+    );
+  });
+});

+ 151 - 0
static/app/components/events/autofix/autofixChanges.spec.tsx

@@ -0,0 +1,151 @@
+import {AutofixCodebaseChangeData} from 'sentry-fixture/autofixCodebaseChangeData';
+
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import {AutofixChanges} from './autofixChanges';
+import {AutofixStatus, AutofixStepType} from './types';
+
+const mockUseAutofix = jest.fn();
+jest.mock('sentry/components/events/autofix/useAutofix', () => ({
+  ...jest.requireActual('sentry/components/events/autofix/useAutofix'),
+  useAutofixData: () => mockUseAutofix(),
+}));
+
+const mockUseAutofixSetup = jest.fn();
+jest.mock('sentry/components/events/autofix/useAutofixSetup', () => ({
+  useAutofixSetup: () => mockUseAutofixSetup(),
+}));
+
+const mockUpdateInsightCard = jest.fn();
+jest.mock('sentry/components/events/autofix/autofixInsightCards', () => ({
+  useUpdateInsightCard: () => ({
+    mutate: mockUpdateInsightCard,
+  }),
+}));
+
+describe('AutofixChanges', () => {
+  const defaultProps = {
+    groupId: '123',
+    runId: 'run-123',
+    step: {
+      id: 'step-123',
+      progress: [],
+      title: 'Changes',
+      type: AutofixStepType.CHANGES as const,
+      index: 0,
+      status: AutofixStatus.COMPLETED,
+      changes: [AutofixCodebaseChangeData({pull_request: undefined})],
+    },
+  };
+
+  beforeEach(() => {
+    mockUseAutofix.mockReturnValue({
+      status: 'COMPLETED',
+      steps: [
+        {
+          type: AutofixStepType.DEFAULT,
+          index: 0,
+          insights: [],
+        },
+      ],
+    });
+
+    mockUseAutofixSetup.mockReturnValue({
+      data: {
+        githubWriteIntegration: {
+          repos: [
+            {
+              owner: 'getsentry',
+              name: 'sentry',
+              ok: true,
+            },
+          ],
+        },
+      },
+    });
+  });
+
+  it('renders error state when step has error', () => {
+    render(
+      <AutofixChanges
+        {...defaultProps}
+        step={{
+          ...defaultProps.step,
+          status: AutofixStatus.ERROR,
+        }}
+      />
+    );
+
+    expect(screen.getByText('Something went wrong.')).toBeInTheDocument();
+  });
+
+  it('renders empty state when no changes', () => {
+    render(
+      <AutofixChanges
+        {...defaultProps}
+        step={{
+          ...defaultProps.step,
+          changes: [],
+        }}
+      />
+    );
+
+    expect(screen.getByText('Could not find a fix.')).toBeInTheDocument();
+  });
+
+  it('renders changes with action buttons', () => {
+    render(<AutofixChanges {...defaultProps} />);
+
+    expect(screen.getByText('Fixes')).toBeInTheDocument();
+    expect(screen.getByText('Add error handling')).toBeInTheDocument();
+    expect(screen.getByText('owner/hello-world')).toBeInTheDocument();
+
+    expect(screen.getByRole('button', {name: 'Add Tests'})).toBeInTheDocument();
+    expect(screen.getByRole('button', {name: 'Check Out Locally'})).toBeInTheDocument();
+    expect(screen.getByRole('button', {name: 'Draft PR'})).toBeInTheDocument();
+  });
+
+  it('shows PR links when PRs are created', () => {
+    const changeWithPR = AutofixCodebaseChangeData({
+      pull_request: {
+        pr_number: 123,
+        pr_url: 'https://github.com/owner/hello-world/pull/123',
+      },
+    });
+
+    render(
+      <AutofixChanges
+        {...defaultProps}
+        step={{
+          ...defaultProps.step,
+          changes: [changeWithPR],
+        }}
+      />
+    );
+
+    expect(
+      screen.getByRole('button', {name: 'View PR in owner/hello-world'})
+    ).toBeInTheDocument();
+  });
+
+  it('shows branch checkout buttons when branches are created', () => {
+    const changeWithBranch = AutofixCodebaseChangeData({
+      branch_name: 'fix/issue-123',
+      pull_request: undefined,
+    });
+
+    render(
+      <AutofixChanges
+        {...defaultProps}
+        step={{
+          ...defaultProps.step,
+          changes: [changeWithBranch],
+        }}
+      />
+    );
+
+    expect(
+      screen.getByRole('button', {name: 'Check out in owner/hello-world'})
+    ).toBeInTheDocument();
+  });
+});

+ 388 - 12
static/app/components/events/autofix/autofixChanges.tsx

@@ -1,33 +1,56 @@
-import {Fragment} from 'react';
+import {Fragment, useState} from 'react';
 import styled from '@emotion/styled';
 import {AnimatePresence, type AnimationProps, motion} from 'framer-motion';
 
+import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
+import {openModal} from 'sentry/actionCreators/modal';
+import {Button, LinkButton} from 'sentry/components/button';
+import ButtonBar from 'sentry/components/buttonBar';
 import ClippedBox from 'sentry/components/clippedBox';
 import {AutofixDiff} from 'sentry/components/events/autofix/autofixDiff';
-import type {
-  AutofixChangesStep,
-  AutofixCodebaseChange,
+import {useUpdateInsightCard} from 'sentry/components/events/autofix/autofixInsightCards';
+import {AutofixSetupWriteAccessModal} from 'sentry/components/events/autofix/autofixSetupWriteAccessModal';
+import {
+  type AutofixChangesStep,
+  type AutofixCodebaseChange,
+  AutofixStatus,
+  AutofixStepType,
 } from 'sentry/components/events/autofix/types';
-import {useAutofixData} from 'sentry/components/events/autofix/useAutofix';
-import {IconFix} from 'sentry/icons';
+import {
+  makeAutofixQueryKey,
+  useAutofixData,
+} from 'sentry/components/events/autofix/useAutofix';
+import {useAutofixSetup} from 'sentry/components/events/autofix/useAutofixSetup';
+import LoadingIndicator from 'sentry/components/loadingIndicator';
+import {ScrollCarousel} from 'sentry/components/scrollCarousel';
+import {IconCopy, IconFix, IconOpen} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
+import {useMutation, useQueryClient} from 'sentry/utils/queryClient';
 import testableTransition from 'sentry/utils/testableTransition';
+import useApi from 'sentry/utils/useApi';
+import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
 
 type AutofixChangesProps = {
   groupId: string;
   runId: string;
   step: AutofixChangesStep;
+  previousDefaultStepIndex?: number;
+  previousInsightCount?: number;
 };
 
 function AutofixRepoChange({
   change,
   groupId,
   runId,
+  previousDefaultStepIndex,
+  previousInsightCount,
 }: {
   change: AutofixCodebaseChange;
   groupId: string;
   runId: string;
+  previousDefaultStepIndex?: number;
+  previousInsightCount?: number;
 }) {
   return (
     <Content>
@@ -43,6 +66,8 @@ function AutofixRepoChange({
         runId={runId}
         repoId={change.repo_external_id}
         editable={!change.pull_request}
+        previousDefaultStepIndex={previousDefaultStepIndex}
+        previousInsightCount={previousInsightCount}
       />
     </Content>
   );
@@ -69,9 +94,250 @@ const cardAnimationProps: AnimationProps = {
   }),
 };
 
-export function AutofixChanges({step, groupId, runId}: AutofixChangesProps) {
+function BranchButton({change}: {change: AutofixCodebaseChange}) {
+  const {onClick} = useCopyToClipboard({
+    text: `git fetch --all && git switch ${change.branch_name}`,
+    successMessage: t('Command copied. Next stop: your terminal.'),
+  });
+
+  return (
+    <Button
+      key={`${change.repo_external_id}-${Math.random()}`}
+      size="xs"
+      priority="primary"
+      onClick={onClick}
+      aria-label={t('Check out in %s', change.repo_name)}
+      title={t('git fetch --all && git switch %s', change.branch_name)}
+      icon={<IconCopy size="xs" />}
+    >
+      {t('Check out in %s', change.repo_name)}
+    </Button>
+  );
+}
+
+function CreatePRsButton({
+  changes,
+  groupId,
+  runId,
+}: {
+  changes: AutofixCodebaseChange[];
+  groupId: string;
+  runId: string;
+}) {
+  const api = useApi();
+  const queryClient = useQueryClient();
+  const [hasClickedCreatePr, setHasClickedCreatePr] = useState(false);
+
+  const createPRs = () => {
+    setHasClickedCreatePr(true);
+    for (const change of changes) {
+      createPr({change});
+    }
+  };
+
+  const {mutate: createPr} = useMutation({
+    mutationFn: ({change}: {change: AutofixCodebaseChange}) => {
+      return api.requestPromise(`/issues/${groupId}/autofix/update/`, {
+        method: 'POST',
+        data: {
+          run_id: runId,
+          payload: {
+            type: 'create_pr',
+            repo_external_id: change.repo_external_id,
+          },
+        },
+      });
+    },
+    onSuccess: () => {
+      addSuccessMessage(t('Created pull requests.'));
+      queryClient.invalidateQueries({queryKey: makeAutofixQueryKey(groupId)});
+    },
+    onError: () => {
+      setHasClickedCreatePr(false);
+      addErrorMessage(t('Failed to create a pull request'));
+    },
+  });
+
+  return (
+    <Button
+      priority="primary"
+      onClick={createPRs}
+      icon={
+        hasClickedCreatePr && <ProcessingStatusIndicator size={14} mini hideMessage />
+      }
+      size="sm"
+      busy={hasClickedCreatePr}
+      analyticsEventName="Autofix: Create PR Clicked"
+      analyticsEventKey="autofix.create_pr_clicked"
+      analyticsParams={{group_id: groupId}}
+    >
+      Draft PR{changes.length > 1 ? 's' : ''}
+    </Button>
+  );
+}
+
+function CreateBranchButton({
+  changes,
+  groupId,
+  runId,
+}: {
+  changes: AutofixCodebaseChange[];
+  groupId: string;
+  runId: string;
+}) {
+  const api = useApi();
+  const queryClient = useQueryClient();
+  const [hasClickedPushToBranch, setHasClickedPushToBranch] = useState(false);
+
+  const pushToBranch = () => {
+    setHasClickedPushToBranch(true);
+    for (const change of changes) {
+      createBranch({change});
+    }
+  };
+
+  const {mutate: createBranch} = useMutation({
+    mutationFn: ({change}: {change: AutofixCodebaseChange}) => {
+      return api.requestPromise(`/issues/${groupId}/autofix/update/`, {
+        method: 'POST',
+        data: {
+          run_id: runId,
+          payload: {
+            type: 'create_branch',
+            repo_external_id: change.repo_external_id,
+          },
+        },
+      });
+    },
+    onSuccess: () => {
+      addSuccessMessage(t('Pushed to branches.'));
+      queryClient.invalidateQueries({queryKey: makeAutofixQueryKey(groupId)});
+    },
+    onError: () => {
+      setHasClickedPushToBranch(false);
+      addErrorMessage(t('Failed to push to branches.'));
+    },
+  });
+
+  return (
+    <Button
+      onClick={pushToBranch}
+      icon={
+        hasClickedPushToBranch && <ProcessingStatusIndicator size={14} mini hideMessage />
+      }
+      size="sm"
+      busy={hasClickedPushToBranch}
+      analyticsEventName="Autofix: Push to Branch Clicked"
+      analyticsEventKey="autofix.push_to_branch_clicked"
+      analyticsParams={{group_id: groupId}}
+    >
+      Check Out Locally
+    </Button>
+  );
+}
+
+function SetupAndCreateBranchButton({
+  changes,
+  groupId,
+  runId,
+}: {
+  changes: AutofixCodebaseChange[];
+  groupId: string;
+  runId: string;
+}) {
+  const {data: setupData} = useAutofixSetup({groupId, checkWriteAccess: true});
+
+  if (
+    !changes.every(
+      change =>
+        setupData?.githubWriteIntegration?.repos?.find(
+          repo => `${repo.owner}/${repo.name}` === change.repo_name
+        )?.ok
+    )
+  ) {
+    return (
+      <Button
+        onClick={() => {
+          openModal(deps => <AutofixSetupWriteAccessModal {...deps} groupId={groupId} />);
+        }}
+        size="sm"
+        analyticsEventName="Autofix: Create Branch Setup Clicked"
+        analyticsEventKey="autofix.create_branch_setup_clicked"
+        analyticsParams={{group_id: groupId}}
+        title={t('Enable write access to create branches')}
+      >
+        {t('Check Out Locally')}
+      </Button>
+    );
+  }
+
+  return <CreateBranchButton changes={changes} groupId={groupId} runId={runId} />;
+}
+
+function SetupAndCreatePRsButton({
+  changes,
+  groupId,
+  runId,
+}: {
+  changes: AutofixCodebaseChange[];
+  groupId: string;
+  runId: string;
+}) {
+  const {data: setupData} = useAutofixSetup({groupId, checkWriteAccess: true});
+
+  if (
+    !changes.every(
+      change =>
+        setupData?.githubWriteIntegration?.repos?.find(
+          repo => `${repo.owner}/${repo.name}` === change.repo_name
+        )?.ok
+    )
+  ) {
+    return (
+      <Button
+        priority="primary"
+        onClick={() => {
+          openModal(deps => <AutofixSetupWriteAccessModal {...deps} groupId={groupId} />);
+        }}
+        size="sm"
+        analyticsEventName="Autofix: Create PR Setup Clicked"
+        analyticsEventKey="autofix.create_pr_setup_clicked"
+        analyticsParams={{group_id: groupId}}
+        title={t('Enable write access to create pull requests')}
+      >
+        {t('Draft PR')}
+      </Button>
+    );
+  }
+
+  return <CreatePRsButton changes={changes} groupId={groupId} runId={runId} />;
+}
+
+export function AutofixChanges({
+  step,
+  groupId,
+  runId,
+  previousDefaultStepIndex,
+  previousInsightCount,
+}: AutofixChangesProps) {
   const data = useAutofixData({groupId});
 
+  const {mutate: sendFeedbackOnChanges} = useUpdateInsightCard({groupId, runId});
+
+  const handleAddTests = () => {
+    const planStep = data?.steps?.[data.steps.length - 2];
+    if (!planStep || planStep.type !== AutofixStepType.DEFAULT) {
+      return;
+    }
+
+    sendFeedbackOnChanges({
+      step_index: planStep.index,
+      retain_insight_card_index: planStep.insights.length - 1,
+      message:
+        'Please write a unit test that reproduces the issue to make sure it is fixed. Put it in the appropriate test file in the codebase. If there is none, create one.',
+    });
+  };
+
   if (step.status === 'ERROR' || data?.status === 'ERROR') {
     return (
       <Content>
@@ -101,19 +367,109 @@ export function AutofixChanges({step, groupId, runId}: AutofixChangesProps) {
 
   const allChangesHavePullRequests = step.changes.every(change => change.pull_request);
 
+  const prsMade =
+    step.status === AutofixStatus.COMPLETED &&
+    step.changes.length >= 1 &&
+    step.changes.every(change => change.pull_request);
+
+  const branchesMade =
+    !prsMade &&
+    step.status === AutofixStatus.COMPLETED &&
+    step.changes.length >= 1 &&
+    step.changes.every(change => change.branch_name);
+
   return (
     <AnimatePresence initial>
       <AnimationWrapper key="card" {...cardAnimationProps}>
         <ChangesContainer allChangesHavePullRequests={allChangesHavePullRequests}>
           <ClippedBox clipHeight={408}>
-            <HeaderText>
-              <IconFix size="sm" />
-              {t('Fixes')}
-            </HeaderText>
+            <HeaderWrapper>
+              <HeaderText>
+                <IconFix size="sm" />
+                {t('Fixes')}
+              </HeaderText>
+              {!prsMade && !branchesMade ? (
+                <ButtonBar gap={1}>
+                  <Button
+                    size="sm"
+                    onClick={handleAddTests}
+                    analyticsEventName="Autofix: Add Tests Clicked"
+                    analyticsEventKey="autofix.add_tests_clicked"
+                    analyticsParams={{group_id: groupId}}
+                  >
+                    {t('Add Tests')}
+                  </Button>
+                  <SetupAndCreateBranchButton
+                    changes={step.changes}
+                    groupId={groupId}
+                    runId={runId}
+                  />
+                  <SetupAndCreatePRsButton
+                    changes={step.changes}
+                    groupId={groupId}
+                    runId={runId}
+                  />
+                </ButtonBar>
+              ) : prsMade ? (
+                step.changes.length === 1 &&
+                step.changes[0] &&
+                step.changes[0].pull_request?.pr_url ? (
+                  <LinkButton
+                    size="xs"
+                    priority="primary"
+                    icon={<IconOpen size="xs" />}
+                    href={step.changes[0].pull_request.pr_url}
+                    external
+                  >
+                    View PR in {step.changes[0].repo_name}
+                  </LinkButton>
+                ) : (
+                  <StyledScrollCarousel aria-label={t('View pull requests')}>
+                    {step.changes.map(
+                      change =>
+                        change.pull_request?.pr_url && (
+                          <LinkButton
+                            key={`${change.repo_external_id}-${Math.random()}`}
+                            size="xs"
+                            priority="primary"
+                            icon={<IconOpen size="xs" />}
+                            href={change.pull_request.pr_url}
+                            external
+                          >
+                            View PR in {change.repo_name}
+                          </LinkButton>
+                        )
+                    )}
+                  </StyledScrollCarousel>
+                )
+              ) : branchesMade ? (
+                step.changes.length === 1 && step.changes[0] ? (
+                  <BranchButton change={step.changes[0]} />
+                ) : (
+                  <StyledScrollCarousel aria-label={t('Check out branches')}>
+                    {step.changes.map(
+                      change =>
+                        change.branch_name && (
+                          <BranchButton
+                            key={`${change.repo_external_id}-${Math.random()}`}
+                            change={change}
+                          />
+                        )
+                    )}
+                  </StyledScrollCarousel>
+                )
+              ) : null}
+            </HeaderWrapper>
             {step.changes.map((change, i) => (
               <Fragment key={change.repo_external_id}>
                 {i > 0 && <Separator />}
-                <AutofixRepoChange change={change} groupId={groupId} runId={runId} />
+                <AutofixRepoChange
+                  change={change}
+                  groupId={groupId}
+                  runId={runId}
+                  previousDefaultStepIndex={previousDefaultStepIndex}
+                  previousInsightCount={previousInsightCount}
+                />
               </Fragment>
             ))}
           </ClippedBox>
@@ -123,6 +479,10 @@ export function AutofixChanges({step, groupId, runId}: AutofixChangesProps) {
   );
 }
 
+const StyledScrollCarousel = styled(ScrollCarousel)`
+  padding: 0 ${space(1)};
+`;
+
 const PreviewContent = styled('div')`
   display: flex;
   flex-direction: column;
@@ -182,3 +542,19 @@ const HeaderText = styled('div')`
   align-items: center;
   gap: ${space(1)};
 `;
+
+const HeaderWrapper = styled('div')`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 ${space(1)} ${space(1)} ${space(1)};
+  border-bottom: 1px solid ${p => p.theme.border};
+`;
+
+const ProcessingStatusIndicator = styled(LoadingIndicator)`
+  && {
+    margin: 0;
+    height: 14px;
+    width: 14px;
+  }
+`;

+ 51 - 14
static/app/components/events/autofix/autofixDiff.tsx

@@ -4,12 +4,14 @@ import {type Change, diffWords} from 'diff';
 
 import {addErrorMessage} from 'sentry/actionCreators/indicator';
 import {Button} from 'sentry/components/button';
+import AutofixHighlightPopup from 'sentry/components/events/autofix/autofixHighlightPopup';
 import {
   type DiffLine,
   DiffLineType,
   type FilePatch,
 } from 'sentry/components/events/autofix/types';
 import {makeAutofixQueryKey} from 'sentry/components/events/autofix/useAutofix';
+import {useTextSelection} from 'sentry/components/events/autofix/useTextSelection';
 import TextArea from 'sentry/components/forms/controls/textarea';
 import InteractionStateLayer from 'sentry/components/interactionStateLayer';
 import {IconChevron, IconClose, IconDelete, IconEdit} from 'sentry/icons';
@@ -23,6 +25,8 @@ type AutofixDiffProps = {
   editable: boolean;
   groupId: string;
   runId: string;
+  previousDefaultStepIndex?: number;
+  previousInsightCount?: number;
   repoId?: string;
 };
 
@@ -539,24 +543,53 @@ function FileDiff({
   );
 }
 
-export function AutofixDiff({diff, groupId, runId, repoId, editable}: AutofixDiffProps) {
+export function AutofixDiff({
+  diff,
+  groupId,
+  runId,
+  repoId,
+  editable,
+  previousDefaultStepIndex,
+  previousInsightCount,
+}: AutofixDiffProps) {
+  const containerRef = useRef<HTMLDivElement>(null);
+  const selection = useTextSelection(containerRef);
+
   if (!diff || !diff.length) {
     return null;
   }
 
   return (
-    <DiffsColumn>
-      {diff.map(file => (
-        <FileDiff
-          key={file.path}
-          file={file}
+    <div>
+      {selection && (
+        <AutofixHighlightPopup
+          selectedText={selection.selectedText}
+          referenceElement={selection.referenceElement}
           groupId={groupId}
           runId={runId}
-          repoId={repoId}
-          editable={editable}
+          stepIndex={previousDefaultStepIndex ?? 0}
+          retainInsightCardIndex={
+            previousInsightCount !== undefined && previousInsightCount >= 0
+              ? previousInsightCount - 1
+              : -1
+          }
         />
-      ))}
-    </DiffsColumn>
+      )}
+      <div ref={containerRef}>
+        <DiffsColumn>
+          {diff.map(file => (
+            <FileDiff
+              key={file.path}
+              file={file}
+              groupId={groupId}
+              runId={runId}
+              repoId={repoId}
+              editable={editable}
+            />
+          ))}
+        </DiffsColumn>
+      </div>
+    </div>
   );
 }
 
@@ -687,17 +720,21 @@ const ActionButton = styled(Button)<{isHovered: boolean}>`
   margin-left: ${space(0.5)};
   font-family: ${p => p.theme.text.family};
   background-color: ${p =>
-    p.isHovered ? p.theme.button.default.background : p.theme.translucentGray100};
-  color: ${p =>
-    p.isHovered ? p.theme.button.default.color : p.theme.translucentGray200};
+    p.isHovered ? p.theme.button.default.background : p.theme.background};
+  color: ${p => (p.isHovered ? p.theme.pink400 : p.theme.textColor)};
   transition:
     background-color 0.2s ease-in-out,
     color 0.2s ease-in-out;
+
+  &:hover {
+    background-color: ${p => p.theme.pink400}10;
+    color: ${p => p.theme.pink400};
+  }
 `;
 
 const EditOverlay = styled('div')`
   position: fixed;
-  bottom: 11rem;
+  bottom: ${space(2)};
   right: ${space(2)};
   left: calc(50% + ${space(2)});
   background: ${p => p.theme.backgroundElevated};

+ 3 - 15
static/app/components/events/autofix/autofixFeedback.tsx

@@ -1,8 +1,6 @@
 import {useRef} from 'react';
-import styled from '@emotion/styled';
 
 import {Button} from 'sentry/components/button';
-import {IconMegaphone} from 'sentry/icons/iconMegaphone';
 import {t} from 'sentry/locale';
 import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';
 
@@ -15,11 +13,9 @@ function AutofixFeedback() {
   }
 
   return (
-    <StyledButton
+    <Button
       ref={buttonRef}
-      size="zero"
-      borderless
-      icon={<IconMegaphone />}
+      size="xs"
       onClick={() =>
         openForm({
           messagePlaceholder: t('How can we make Autofix better for you?'),
@@ -31,16 +27,8 @@ function AutofixFeedback() {
       }
     >
       {t('Give Feedback')}
-    </StyledButton>
+    </Button>
   );
 }
 
-const StyledButton = styled(Button)`
-  padding: 0;
-  margin: 0;
-  font-size: ${p => p.theme.fontSizeSmall};
-  font-weight: ${p => p.theme.fontWeightNormal};
-  color: ${p => p.theme.subText};
-`;
-
 export default AutofixFeedback;

+ 466 - 0
static/app/components/events/autofix/autofixHighlightPopup.tsx

@@ -0,0 +1,466 @@
+import {
+  startTransition,
+  useEffect,
+  useLayoutEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react';
+import {createPortal} from 'react-dom';
+import styled from '@emotion/styled';
+import {useMutation, useQueryClient} from '@tanstack/react-query';
+import {motion} from 'framer-motion';
+
+import {addErrorMessage} from 'sentry/actionCreators/indicator';
+import {SeerIcon} from 'sentry/components/ai/SeerIcon';
+import UserAvatar from 'sentry/components/avatar/userAvatar';
+import {Button} from 'sentry/components/button';
+import {
+  makeAutofixQueryKey,
+  useAutofixData,
+} from 'sentry/components/events/autofix/useAutofix';
+import Input from 'sentry/components/input';
+import LoadingIndicator from 'sentry/components/loadingIndicator';
+import {IconChevron} from 'sentry/icons';
+import {t} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import testableTransition from 'sentry/utils/testableTransition';
+import useApi from 'sentry/utils/useApi';
+import {useUser} from 'sentry/utils/useUser';
+
+import type {CommentThreadMessage} from './types';
+
+interface Props {
+  groupId: string;
+  referenceElement: HTMLElement | null;
+  retainInsightCardIndex: number | null;
+  runId: string;
+  selectedText: string;
+  stepIndex: number;
+}
+
+interface OptimisticMessage extends CommentThreadMessage {
+  isLoading?: boolean;
+}
+
+function useCommentThread({groupId, runId}: {groupId: string; runId: string}) {
+  const api = useApi({persistInFlight: true});
+  const queryClient = useQueryClient();
+
+  return useMutation({
+    mutationFn: (params: {
+      message: string;
+      retain_insight_card_index: number | null;
+      selected_text: string;
+      step_index: number;
+      thread_id: string;
+    }) => {
+      return api.requestPromise(`/issues/${groupId}/autofix/update/`, {
+        method: 'POST',
+        data: {
+          run_id: runId,
+          payload: {
+            type: 'comment_thread',
+            message: params.message,
+            thread_id: params.thread_id,
+            selected_text: params.selected_text,
+            step_index: params.step_index,
+            retain_insight_card_index: params.retain_insight_card_index,
+          },
+        },
+      });
+    },
+    onSuccess: _ => {
+      queryClient.invalidateQueries({queryKey: makeAutofixQueryKey(groupId)});
+    },
+    onError: () => {
+      addErrorMessage(t('Something went wrong when sending your comment.'));
+    },
+  });
+}
+
+function AutofixHighlightPopupContent({
+  selectedText,
+  groupId,
+  runId,
+  stepIndex,
+  retainInsightCardIndex,
+}: Omit<Props, 'referenceElement'>) {
+  const {mutate: submitComment} = useCommentThread({groupId, runId});
+  const [comment, setComment] = useState('');
+  const [threadId] = useState(() => {
+    const timestamp = Date.now();
+    const random = Math.floor(Math.random() * 10000);
+    return `thread-${timestamp}-${random}`;
+  });
+  const [optimisticMessages, setOptimisticMessages] = useState<OptimisticMessage[]>([]);
+  const messagesEndRef = useRef<HTMLDivElement>(null);
+
+  // Fetch current autofix data to get comment thread
+  const autofixData = useAutofixData({groupId});
+  const currentStep = autofixData?.steps?.[stepIndex];
+  const commentThread =
+    currentStep?.active_comment_thread?.id === threadId
+      ? currentStep.active_comment_thread
+      : null;
+  const messages = useMemo(
+    () => commentThread?.messages ?? [],
+    [commentThread?.messages]
+  );
+
+  // Combine server messages with optimistic ones
+  const allMessages = useMemo(
+    () => [...messages, ...optimisticMessages],
+    [messages, optimisticMessages]
+  );
+
+  const truncatedText =
+    selectedText.length > 70
+      ? selectedText.slice(0, 35).split(' ').slice(0, -1).join(' ') +
+        '... ...' +
+        selectedText.slice(-35)
+      : selectedText;
+
+  const currentUser = useUser();
+
+  const handleSubmit = (e: React.FormEvent) => {
+    e.preventDefault();
+    if (!comment.trim()) {
+      return;
+    }
+
+    // Add user message and loading assistant message immediately
+    setOptimisticMessages([
+      {role: 'user', content: comment},
+      {role: 'assistant', content: '', isLoading: true},
+    ]);
+
+    submitComment({
+      message: comment,
+      thread_id: threadId,
+      selected_text: selectedText,
+      step_index: stepIndex,
+      retain_insight_card_index: retainInsightCardIndex,
+    });
+    setComment('');
+  };
+
+  // Clear optimistic messages when we get new server messages
+  useEffect(() => {
+    if (messages.length > 0) {
+      setOptimisticMessages([]);
+    }
+  }, [messages]);
+
+  const handleContainerClick = (e: React.MouseEvent) => {
+    e.stopPropagation();
+  };
+
+  const scrollToBottom = () => {
+    messagesEndRef.current?.scrollIntoView({behavior: 'smooth'});
+  };
+
+  // Add effect to scroll to bottom when messages change
+  useEffect(() => {
+    scrollToBottom();
+  }, [allMessages]);
+
+  return (
+    <Container onClick={handleContainerClick}>
+      <Header>
+        <SelectedText>
+          <span>"{truncatedText}"</span>
+        </SelectedText>
+      </Header>
+
+      {allMessages.length > 0 && (
+        <MessagesContainer>
+          {allMessages.map((message, i) => (
+            <Message key={i} role={message.role}>
+              {message.role === 'assistant' ? (
+                <CircularSeerIcon>
+                  <SeerIcon />
+                </CircularSeerIcon>
+              ) : (
+                <UserAvatar user={currentUser} size={24} />
+              )}
+              <MessageContent>
+                {message.isLoading ? (
+                  <LoadingWrapper>
+                    <LoadingIndicator mini size={12} />
+                  </LoadingWrapper>
+                ) : (
+                  message.content
+                )}
+              </MessageContent>
+            </Message>
+          ))}
+          <div ref={messagesEndRef} />
+        </MessagesContainer>
+      )}
+
+      {commentThread?.is_completed !== true && (
+        <InputWrapper onSubmit={handleSubmit}>
+          <StyledInput
+            placeholder={t('Questions or comments?')}
+            value={comment}
+            onChange={e => setComment(e.target.value)}
+            size="sm"
+            autoFocus
+          />
+          <StyledButton
+            size="zero"
+            type="submit"
+            borderless
+            aria-label={t('Submit Comment')}
+          >
+            <IconChevron direction="right" />
+          </StyledButton>
+        </InputWrapper>
+      )}
+    </Container>
+  );
+}
+
+function AutofixHighlightPopup(props: Props) {
+  const {referenceElement} = props;
+  const popupRef = useRef<HTMLDivElement>(null);
+  const [position, setPosition] = useState({left: 0, top: 0});
+
+  useLayoutEffect(() => {
+    if (!referenceElement || !popupRef.current) {
+      return undefined;
+    }
+
+    const updatePosition = () => {
+      const rect = referenceElement.getBoundingClientRect();
+      startTransition(() => {
+        setPosition({
+          left: rect.left - 320,
+          top: rect.top,
+        });
+      });
+    };
+
+    // Initial position
+    updatePosition();
+
+    // Create observer to track reference element changes
+    const resizeObserver = new ResizeObserver(updatePosition);
+    resizeObserver.observe(referenceElement);
+
+    // Track scroll events
+    const scrollElements = [window, ...getScrollParents(referenceElement)];
+    scrollElements.forEach(element => {
+      element.addEventListener('scroll', updatePosition, {passive: true});
+    });
+
+    return () => {
+      resizeObserver.disconnect();
+      scrollElements.forEach(element => {
+        element.removeEventListener('scroll', updatePosition);
+      });
+    };
+  }, [referenceElement]);
+
+  return createPortal(
+    <Wrapper
+      ref={popupRef}
+      id="autofix-rethink-input"
+      data-popup="autofix-highlight"
+      initial={{opacity: 0, x: -10}}
+      animate={{opacity: 1, x: 0}}
+      exit={{opacity: 0, x: -10}}
+      transition={testableTransition({
+        duration: 0.2,
+      })}
+      style={{
+        left: `${position.left}px`,
+        top: `${position.top}px`,
+        transform: 'none',
+      }}
+    >
+      <Arrow />
+      <ScaleContainer>
+        <AutofixHighlightPopupContent {...props} />
+      </ScaleContainer>
+    </Wrapper>,
+    document.body
+  );
+}
+
+const Wrapper = styled(motion.div)`
+  z-index: ${p => p.theme.zIndex.tooltip};
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  margin-right: ${space(1)};
+  gap: ${space(1)};
+  width: 300px;
+  position: fixed;
+  will-change: transform;
+`;
+
+const ScaleContainer = styled(motion.div)`
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  transform-origin: top left;
+  padding-left: ${space(2)};
+`;
+
+const Container = styled(motion.div)`
+  position: relative;
+  width: 100%;
+  border-radius: ${p => p.theme.borderRadius};
+  background: ${p => p.theme.background};
+  border: 1px dashed ${p => p.theme.border};
+  overflow: hidden;
+  box-shadow: ${p => p.theme.dropShadowHeavy};
+
+  &:before {
+    content: '';
+    position: absolute;
+    inset: 0;
+    background: linear-gradient(
+      90deg,
+      transparent,
+      ${p => p.theme.active}20,
+      transparent
+    );
+    background-size: 2000px 100%;
+    pointer-events: none;
+  }
+`;
+
+const InputWrapper = styled('form')`
+  display: flex;
+  padding: ${space(0.5)};
+  background: ${p => p.theme.backgroundSecondary};
+  position: relative;
+`;
+
+const StyledInput = styled(Input)`
+  flex-grow: 1;
+  background: ${p => p.theme.background}
+    linear-gradient(to left, ${p => p.theme.background}, ${p => p.theme.pink400}20);
+  border-color: ${p => p.theme.innerBorder};
+  padding-right: ${space(4)};
+
+  &:hover {
+    border-color: ${p => p.theme.border};
+  }
+`;
+
+const StyledButton = styled(Button)`
+  position: absolute;
+  right: ${space(1)};
+  top: 50%;
+  transform: translateY(-50%);
+  height: 24px;
+  width: 24px;
+  margin-right: 0;
+
+  z-index: 2;
+`;
+
+const Header = styled('div')`
+  display: flex;
+  align-items: center;
+  gap: ${space(1)};
+  padding: ${space(1)};
+  background: ${p => p.theme.backgroundSecondary};
+  word-break: break-word;
+  overflow-wrap: break-word;
+`;
+
+const SelectedText = styled('div')`
+  font-size: ${p => p.theme.fontSizeSmall};
+  color: ${p => p.theme.subText};
+  display: flex;
+  align-items: center;
+
+  span {
+    overflow: wrap;
+    white-space: wrap;
+  }
+`;
+
+const Arrow = styled('div')`
+  position: absolute;
+  width: 12px;
+  height: 12px;
+  background: ${p => p.theme.active}01;
+  border: 1px dashed ${p => p.theme.border};
+  border-right: none;
+  border-bottom: none;
+  top: 20px;
+  right: -6px;
+  transform: rotate(135deg);
+`;
+
+const MessagesContainer = styled('div')`
+  padding: ${space(1)};
+  display: flex;
+  flex-direction: column;
+  gap: ${space(0.5)};
+  max-height: 200px;
+  overflow-y: auto;
+  scroll-behavior: smooth;
+`;
+
+const Message = styled('div')<{role: CommentThreadMessage['role']}>`
+  display: flex;
+  gap: ${space(1)};
+  align-items: flex-start;
+`;
+
+const MessageContent = styled('div')`
+  flex-grow: 1;
+  border-radius: ${p => p.theme.borderRadius};
+  padding-top: ${space(0.5)};
+  font-size: ${p => p.theme.fontSizeSmall};
+  color: ${p => p.theme.textColor};
+`;
+
+const CircularSeerIcon = styled('div')`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  background: ${p => p.theme.purple300};
+  flex-shrink: 0;
+
+  > svg {
+    width: 14px;
+    height: 14px;
+    color: ${p => p.theme.white};
+  }
+`;
+
+const LoadingWrapper = styled('div')`
+  display: flex;
+  align-items: center;
+  height: 24px;
+  margin-top: ${space(0.25)};
+`;
+
+function getScrollParents(element: HTMLElement): Element[] {
+  const scrollParents: Element[] = [];
+  let currentElement = element.parentElement;
+
+  while (currentElement) {
+    const overflow = window.getComputedStyle(currentElement).overflow;
+    if (overflow.includes('scroll') || overflow.includes('auto')) {
+      scrollParents.push(currentElement);
+    }
+    currentElement = currentElement.parentElement;
+  }
+
+  return scrollParents;
+}
+
+export default AutofixHighlightPopup;

+ 38 - 124
static/app/components/events/autofix/autofixInsightCards.spec.tsx

@@ -4,60 +4,16 @@ import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicato
 import AutofixInsightCards from 'sentry/components/events/autofix/autofixInsightCards';
 import type {AutofixInsight} from 'sentry/components/events/autofix/types';
 
-jest.mock('sentry/utils/marked', () => ({
-  singleLineRenderer: jest.fn(text => text),
-}));
-
 jest.mock('sentry/actionCreators/indicator');
 
 const sampleInsights: AutofixInsight[] = [
   {
-    breadcrumb_context: [
-      {
-        body: 'Breadcrumb body',
-        category: 'ui',
-        level: 'info',
-        data_as_json: '{"testData": "testValue"}',
-        type: 'info',
-      },
-    ],
-    codebase_context: [
-      {
-        snippet: 'console.log("Hello, World!");',
-        repo_name: 'sample-repo',
-        file_path: 'src/index.js',
-      },
-    ],
     insight: 'Sample insight 1',
     justification: 'Sample justification 1',
-    stacktrace_context: [
-      {
-        code_snippet: 'function() { throw new Error("Test error"); }',
-        repo_name: 'sample-repo',
-        file_name: 'src/error.js',
-        vars_as_json: '{"testVar": "testValue"}',
-        col_no: 1,
-        line_no: 1,
-        function: 'testFunction',
-      },
-    ],
   },
   {
     insight: 'User message',
     justification: 'USER',
-    breadcrumb_context: [],
-    stacktrace_context: [],
-    codebase_context: [],
-  },
-];
-
-const sampleRepos = [
-  {
-    external_id: '1',
-    name: 'sample-repo',
-    default_branch: 'main',
-    provider: 'github',
-    url: 'github.com/org/sample-repo',
   },
 ];
 
@@ -72,7 +28,6 @@ describe('AutofixInsightCards', () => {
     return render(
       <AutofixInsightCards
         insights={sampleInsights}
-        repos={sampleRepos}
         hasStepAbove={false}
         hasStepBelow={false}
         groupId="1"
@@ -89,33 +44,6 @@ describe('AutofixInsightCards', () => {
     expect(screen.getByText('User message')).toBeInTheDocument();
   });
 
-  it('renders breadcrumb context correctly', async () => {
-    renderComponent();
-    const contextButton = screen.getByText('Sample insight 1');
-    await userEvent.click(contextButton);
-    expect(screen.getByText('Breadcrumb body')).toBeInTheDocument();
-    expect(screen.getByText('info')).toBeInTheDocument();
-  });
-
-  it('renders codebase context correctly', async () => {
-    renderComponent();
-    const contextButton = screen.getByText('Sample insight 1');
-    await userEvent.click(contextButton);
-    expect(screen.getByText('console.log("Hello, World!");')).toBeInTheDocument();
-    expect(screen.getByText('src/index.js')).toBeInTheDocument();
-  });
-
-  it('renders stacktrace context correctly', async () => {
-    renderComponent();
-    const contextButton = screen.getByText('Sample insight 1');
-    await userEvent.click(contextButton);
-    expect(
-      screen.getByText('function() { throw new Error("Test error"); }')
-    ).toBeInTheDocument();
-    expect(screen.getByText('src/error.js')).toBeInTheDocument();
-    expect(screen.getByText('testVar')).toBeInTheDocument();
-  });
-
   it('renders user messages differently', () => {
     renderComponent();
     const userMessage = screen.getByText('User message');
@@ -127,10 +55,14 @@ describe('AutofixInsightCards', () => {
     const contextButton = screen.getByText('Sample insight 1');
 
     await userEvent.click(contextButton);
-    expect(screen.getByText('Sample justification 1')).toBeInTheDocument();
+    await waitFor(() => {
+      expect(screen.getByText('Sample justification 1')).toBeInTheDocument();
+    });
 
     await userEvent.click(contextButton);
-    expect(screen.queryByText('Sample justification 1')).not.toBeInTheDocument();
+    await waitFor(() => {
+      expect(screen.queryByText('Sample justification 1')).not.toBeInTheDocument();
+    });
   });
 
   it('renders multiple insights correctly', () => {
@@ -147,59 +79,49 @@ describe('AutofixInsightCards', () => {
     expect(screen.getByText('Another insight')).toBeInTheDocument();
   });
 
-  it('renders "Rethink from here" buttons', () => {
+  it('renders "Edit insight" buttons', () => {
     renderComponent();
-    const rethinkButtons = screen.getAllByRole('button', {name: 'Rethink from here'});
-    expect(rethinkButtons.length).toBeGreaterThan(0);
+    const editButtons = screen.getAllByRole('button', {name: 'Edit insight'});
+    expect(editButtons.length).toBeGreaterThan(0);
   });
 
-  it('shows rethink input overlay when "Rethink from here" is clicked', async () => {
+  it('shows edit input overlay when "Edit insight" is clicked', async () => {
     renderComponent();
-    const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
-    await userEvent.click(rethinkButton);
+    const editButton = screen.getAllByRole('button', {name: 'Edit insight'})[0]!;
+    await userEvent.click(editButton);
     expect(
-      screen.getByPlaceholderText(
-        'You should know X... Dive deeper into Y... Look at Z...'
-      )
+      screen.getByPlaceholderText('Share your own insight here...')
     ).toBeInTheDocument();
   });
 
-  it('hides rethink input overlay when clicked outside', async () => {
+  it('hides edit input when clicked cancel', async () => {
     renderComponent();
-    const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
-    await userEvent.click(rethinkButton);
+    const editButton = screen.getAllByRole('button', {name: 'Edit insight'})[0]!;
+    await userEvent.click(editButton);
     expect(
-      screen.getByPlaceholderText(
-        'You should know X... Dive deeper into Y... Look at Z...'
-      )
+      screen.getByPlaceholderText('Share your own insight here...')
     ).toBeInTheDocument();
 
-    await userEvent.click(document.body);
+    await userEvent.click(screen.getByLabelText('Cancel'));
     expect(
-      screen.queryByPlaceholderText(
-        'You should know X... Dive deeper into Y... Look at Z...'
-      )
+      screen.queryByPlaceholderText('Share your own insight here...')
     ).not.toBeInTheDocument();
   });
 
-  it('submits rethink request when form is submitted', async () => {
+  it('submits edit request when form is submitted', async () => {
     const mockApi = MockApiClient.addMockResponse({
       url: '/issues/1/autofix/update/',
       method: 'POST',
     });
 
     renderComponent();
-    const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
-    await userEvent.click(rethinkButton);
+    const editButton = screen.getAllByRole('button', {name: 'Edit insight'})[1]!;
+    await userEvent.click(editButton);
 
-    const input = screen.getByPlaceholderText(
-      'You should know X... Dive deeper into Y... Look at Z...'
-    );
-    await userEvent.type(input, 'Rethink this part');
+    const input = screen.getByPlaceholderText('Share your own insight here...');
+    await userEvent.type(input, 'Here is my insight.');
 
-    const submitButton = screen.getByLabelText(
-      'Restart analysis from this point in the chain'
-    );
+    const submitButton = screen.getByLabelText('Rethink from here using your insight');
     await userEvent.click(submitButton);
 
     expect(mockApi).toHaveBeenCalledWith(
@@ -210,7 +132,7 @@ describe('AutofixInsightCards', () => {
           run_id: '1',
           payload: expect.objectContaining({
             type: 'restart_from_point_with_feedback',
-            message: 'Rethink this part',
+            message: 'Here is my insight.',
             step_index: 0,
             retain_insight_card_index: 0,
           }),
@@ -219,24 +141,20 @@ describe('AutofixInsightCards', () => {
     );
   });
 
-  it('shows success message after successful rethink submission', async () => {
+  it('shows success message after successful edit submission', async () => {
     MockApiClient.addMockResponse({
       url: '/issues/1/autofix/update/',
       method: 'POST',
     });
 
     renderComponent();
-    const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
-    await userEvent.click(rethinkButton);
+    const editButton = screen.getAllByRole('button', {name: 'Edit insight'})[0]!;
+    await userEvent.click(editButton);
 
-    const input = screen.getByPlaceholderText(
-      'You should know X... Dive deeper into Y... Look at Z...'
-    );
-    await userEvent.type(input, 'Rethink this part');
+    const input = screen.getByPlaceholderText('Share your own insight here...');
+    await userEvent.type(input, 'Here is my insight.');
 
-    const submitButton = screen.getByLabelText(
-      'Restart analysis from this point in the chain'
-    );
+    const submitButton = screen.getByLabelText('Rethink from here using your insight');
     await userEvent.click(submitButton);
 
     await waitFor(() => {
@@ -244,7 +162,7 @@ describe('AutofixInsightCards', () => {
     });
   });
 
-  it('shows error message after failed rethink submission', async () => {
+  it('shows error message after failed edit submission', async () => {
     MockApiClient.addMockResponse({
       url: '/issues/1/autofix/update/',
       method: 'POST',
@@ -252,17 +170,13 @@ describe('AutofixInsightCards', () => {
     });
 
     renderComponent();
-    const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
-    await userEvent.click(rethinkButton);
+    const editButton = screen.getAllByRole('button', {name: 'Edit insight'})[0]!;
+    await userEvent.click(editButton);
 
-    const input = screen.getByPlaceholderText(
-      'You should know X... Dive deeper into Y... Look at Z...'
-    );
-    await userEvent.type(input, 'Rethink this part');
+    const input = screen.getByPlaceholderText('Share your own insight here...');
+    await userEvent.type(input, 'Here is my insight.');
 
-    const submitButton = screen.getByLabelText(
-      'Restart analysis from this point in the chain'
-    );
+    const submitButton = screen.getByLabelText('Rethink from here using your insight');
     await userEvent.click(submitButton);
 
     await waitFor(() => {

File diff suppressed because it is too large
+ 360 - 481
static/app/components/events/autofix/autofixInsightCards.tsx


+ 0 - 179
static/app/components/events/autofix/autofixMessageBox.analytics.spec.tsx

@@ -1,179 +0,0 @@
-import {AutofixCodebaseChangeData} from 'sentry-fixture/autofixCodebaseChangeData';
-import {AutofixStepFixture} from 'sentry-fixture/autofixStep';
-
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-
-import {Button} from 'sentry/components/button';
-import AutofixMessageBox from 'sentry/components/events/autofix/autofixMessageBox';
-import {AutofixStepType} from 'sentry/components/events/autofix/types';
-
-jest.mock('sentry/components/button', () => ({
-  Button: jest.fn(props => {
-    // Forward the click handler while allowing us to inspect props
-    return <button onClick={props.onClick}>{props.children}</button>;
-  }),
-  LinkButton: jest.fn(props => {
-    return <a href={props.href}>{props.children}</a>;
-  }),
-}));
-
-const mockButton = Button as jest.MockedFunction<typeof Button>;
-
-describe('AutofixMessageBox Analytics', () => {
-  const defaultProps = {
-    displayText: 'Test display text',
-    groupId: '123',
-    runId: '456',
-    actionText: 'Send',
-    allowEmptyMessage: false,
-    responseRequired: false,
-    step: null,
-    onSend: null,
-  };
-
-  const changesStepProps = {
-    ...defaultProps,
-    isChangesStep: true,
-    step: AutofixStepFixture({
-      type: AutofixStepType.CHANGES,
-      changes: [AutofixCodebaseChangeData()],
-    }),
-  };
-
-  beforeEach(() => {
-    MockApiClient.clearMockResponses();
-    mockButton.mockClear();
-  });
-
-  it('passes correct analytics props for suggested root cause without instructions', async () => {
-    const onSendMock = jest.fn();
-    render(
-      <AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
-    );
-
-    await userEvent.click(screen.getByRole('button', {name: 'Use suggested root cause'}));
-
-    expect(mockButton).toHaveBeenLastCalledWith(
-      expect.objectContaining({
-        analyticsEventKey: 'autofix.create_fix_clicked',
-        analyticsEventName: 'Autofix: Create Fix Clicked',
-        analyticsParams: {
-          group_id: '123',
-          type: 'suggested',
-        },
-      }),
-      expect.anything()
-    );
-  });
-
-  it('passes correct analytics props for suggested root cause with instructions', async () => {
-    const onSendMock = jest.fn();
-    render(
-      <AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
-    );
-
-    await userEvent.click(screen.getByRole('button', {name: 'Use suggested root cause'}));
-
-    const input = screen.getByPlaceholderText(
-      '(Optional) Provide any instructions for the fix...'
-    );
-    await userEvent.type(input, 'Some instructions');
-
-    expect(mockButton).toHaveBeenLastCalledWith(
-      expect.objectContaining({
-        analyticsEventKey: 'autofix.create_fix_clicked',
-        analyticsEventName: 'Autofix: Create Fix Clicked',
-        analyticsParams: {
-          group_id: '123',
-          type: 'suggested_with_instructions',
-        },
-      }),
-      expect.anything()
-    );
-  });
-
-  it('passes correct analytics props for custom root cause', async () => {
-    const onSendMock = jest.fn();
-    render(
-      <AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
-    );
-
-    await userEvent.click(screen.getAllByText('Propose your own root cause')[0]!);
-    const customInput = screen.getByPlaceholderText('Propose your own root cause...');
-    await userEvent.type(customInput, 'Custom root cause');
-
-    expect(mockButton).toHaveBeenLastCalledWith(
-      expect.objectContaining({
-        analyticsEventKey: 'autofix.create_fix_clicked',
-        analyticsEventName: 'Autofix: Create Fix Clicked',
-        analyticsParams: {
-          group_id: '123',
-          type: 'custom',
-        },
-      }),
-      expect.anything()
-    );
-  });
-
-  it('passes correct analytics props for Create PR button', async () => {
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/setup/?check_write_access=true',
-      method: 'GET',
-      body: {
-        genAIConsent: {ok: true},
-        integration: {ok: true},
-        githubWriteIntegration: {
-          repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
-        },
-      },
-    });
-
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Use this code'}));
-
-    // Find the last call to Button that matches our Create PR button
-    const createPRButtonCall = mockButton.mock.calls.find(
-      call => call[0]?.analyticsEventKey === 'autofix.create_pr_clicked'
-    );
-    expect(createPRButtonCall?.[0]).toEqual(
-      expect.objectContaining({
-        analyticsEventKey: 'autofix.create_pr_clicked',
-        analyticsEventName: 'Autofix: Create PR Clicked',
-        analyticsParams: {group_id: '123'},
-      })
-    );
-  });
-
-  it('passes correct analytics props for Create PR Setup button', async () => {
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/setup/?check_write_access=true',
-      method: 'GET',
-      body: {
-        genAIConsent: {ok: true},
-        integration: {ok: true},
-        githubWriteIntegration: {
-          repos: [{ok: false, owner: 'owner', name: 'hello-world', id: 100}],
-        },
-      },
-    });
-
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Use this code'}));
-
-    // Find the last call to Button that matches our Setup button
-    const setupButtonCall = mockButton.mock.calls.find(
-      call => call[0].children === 'Draft PR'
-    );
-    expect(setupButtonCall?.[0]).toEqual(
-      expect.objectContaining({
-        analyticsEventKey: 'autofix.create_pr_setup_clicked',
-        analyticsEventName: 'Autofix: Create PR Setup Clicked',
-        analyticsParams: {
-          group_id: '123',
-        },
-      })
-    );
-  });
-});

+ 0 - 373
static/app/components/events/autofix/autofixMessageBox.spec.tsx

@@ -1,373 +0,0 @@
-import {AutofixCodebaseChangeData} from 'sentry-fixture/autofixCodebaseChangeData';
-import {AutofixStepFixture} from 'sentry-fixture/autofixStep';
-
-import {
-  render,
-  renderGlobalModal,
-  screen,
-  userEvent,
-  waitFor,
-  within,
-} from 'sentry-test/reactTestingLibrary';
-
-import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
-import AutofixMessageBox from 'sentry/components/events/autofix/autofixMessageBox';
-import {AutofixStatus, AutofixStepType} from 'sentry/components/events/autofix/types';
-
-jest.mock('sentry/actionCreators/indicator');
-
-describe('AutofixMessageBox', () => {
-  const defaultProps = {
-    displayText: 'Test display text',
-    groupId: '123',
-    runId: '456',
-    actionText: 'Send',
-    allowEmptyMessage: false,
-    responseRequired: false,
-    step: null,
-    onSend: null,
-  };
-
-  const changesStepProps = {
-    ...defaultProps,
-    isChangesStep: true,
-    step: AutofixStepFixture({
-      type: AutofixStepType.CHANGES,
-      changes: [AutofixCodebaseChangeData()],
-    }),
-  };
-
-  const prCreatedProps = {
-    ...changesStepProps,
-    step: AutofixStepFixture({
-      type: AutofixStepType.CHANGES,
-      status: AutofixStatus.COMPLETED,
-      changes: [AutofixCodebaseChangeData()],
-    }),
-  };
-
-  const multiplePRsProps = {
-    ...changesStepProps,
-    step: AutofixStepFixture({
-      type: AutofixStepType.CHANGES,
-      status: AutofixStatus.COMPLETED,
-      changes: [
-        AutofixCodebaseChangeData({
-          repo_name: 'example/repo1',
-          pull_request: {
-            pr_url: 'https://github.com/example/repo1/pull/1',
-            pr_number: 1,
-          },
-        }),
-        AutofixCodebaseChangeData({
-          repo_name: 'example/repo1',
-          pull_request: {
-            pr_url: 'https://github.com/example/repo2/pull/2',
-            pr_number: 2,
-          },
-        }),
-      ],
-    }),
-  };
-
-  beforeEach(() => {
-    (addSuccessMessage as jest.Mock).mockClear();
-    (addErrorMessage as jest.Mock).mockClear();
-    MockApiClient.clearMockResponses();
-
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/setup/?check_write_access=true',
-      method: 'GET',
-      body: {
-        genAIConsent: {ok: true},
-        integration: {ok: true},
-      },
-    });
-  });
-
-  it('renders correctly with default props', () => {
-    render(<AutofixMessageBox {...defaultProps} />);
-
-    expect(screen.getByText('Test display text')).toBeInTheDocument();
-    expect(
-      screen.getByPlaceholderText('Share helpful context or directions...')
-    ).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Send'})).toBeInTheDocument();
-  });
-
-  it('calls onSend when provided and button is clicked', async () => {
-    const onSendMock = jest.fn();
-    render(<AutofixMessageBox {...defaultProps} onSend={onSendMock} />);
-
-    const input = screen.getByPlaceholderText('Share helpful context or directions...');
-    await userEvent.type(input, 'Test message');
-    await userEvent.click(screen.getByRole('button', {name: 'Send'}));
-
-    expect(onSendMock).toHaveBeenCalledWith('Test message');
-  });
-
-  it('sends interjection message when onSend is not provided', async () => {
-    MockApiClient.addMockResponse({
-      method: 'POST',
-      url: '/issues/123/autofix/update/',
-      body: {},
-    });
-
-    render(<AutofixMessageBox {...defaultProps} />);
-
-    const input = screen.getByPlaceholderText('Share helpful context or directions...');
-    await userEvent.type(input, 'Test message');
-    await userEvent.click(screen.getByRole('button', {name: 'Send'}));
-
-    await waitFor(() => {
-      expect(addSuccessMessage).toHaveBeenCalledWith('Thanks for the input.');
-    });
-  });
-
-  it('displays error message when API request fails', async () => {
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/update/',
-      method: 'POST',
-      body: {
-        detail: 'Internal Error',
-      },
-      statusCode: 500,
-    });
-
-    render(<AutofixMessageBox {...defaultProps} />);
-
-    const input = screen.getByPlaceholderText('Share helpful context or directions...');
-    await userEvent.type(input, 'Test message');
-    await userEvent.click(screen.getByRole('button', {name: 'Send'}));
-
-    await waitFor(() => {
-      expect(addErrorMessage).toHaveBeenCalledWith(
-        'Something went wrong when sending Autofix your message.'
-      );
-    });
-  });
-
-  it('renders step icon and title when step is provided', () => {
-    const stepProps = {
-      ...defaultProps,
-      step: AutofixStepFixture(),
-    };
-
-    render(<AutofixMessageBox {...stepProps} />);
-
-    expect(screen.getByText(AutofixStepFixture().title)).toBeInTheDocument();
-  });
-
-  it('renders required input style when responseRequired is true', () => {
-    render(<AutofixMessageBox {...defaultProps} responseRequired />);
-
-    expect(
-      screen.getByPlaceholderText('Please answer to continue...')
-    ).toBeInTheDocument();
-  });
-
-  it('handles suggested root cause selection correctly', async () => {
-    const onSendMock = jest.fn();
-    render(
-      <AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
-    );
-
-    // Test suggested root cause
-    await userEvent.click(screen.getByRole('button', {name: 'Use suggested root cause'}));
-    const input = screen.getByPlaceholderText(
-      '(Optional) Provide any instructions for the fix...'
-    );
-    await userEvent.type(input, 'Use this suggestion');
-    await userEvent.click(screen.getByRole('button', {name: 'Send'}));
-
-    expect(onSendMock).toHaveBeenCalledWith('Use this suggestion', false);
-  });
-
-  it('handles custom root cause selection correctly', async () => {
-    const onSendMock = jest.fn();
-    render(
-      <AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
-    );
-
-    // Test custom root cause
-    await userEvent.click(
-      screen.getByRole('button', {name: 'Propose your own root cause'})
-    );
-    const customInput = screen.getByPlaceholderText('Propose your own root cause...');
-    await userEvent.type(customInput, 'Custom root cause');
-    await userEvent.click(screen.getByRole('button', {name: 'Send'}));
-
-    expect(onSendMock).toHaveBeenCalledWith('Custom root cause', true);
-  });
-
-  it('renders segmented control for changes step', () => {
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    expect(screen.getByRole('button', {name: 'Iterate'})).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Use this code'})).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Add tests'})).toBeInTheDocument();
-  });
-
-  it('shows feedback input when "Iterate" is selected', async () => {
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Iterate'}));
-
-    expect(
-      screen.getByPlaceholderText('Share helpful context or directions...')
-    ).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Send'})).toBeInTheDocument();
-  });
-
-  it('shows "Draft PR" button when "Approve" is selected', async () => {
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/setup/?check_write_access=true',
-      method: 'GET',
-      body: {
-        genAIConsent: {ok: true},
-        integration: {ok: true},
-        githubWriteIntegration: {
-          repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
-        },
-      },
-    });
-
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Use this code'}));
-
-    expect(screen.getByText('Push the above changes to a branch?')).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Draft PR'})).toBeInTheDocument();
-  });
-
-  it('shows "Draft PRs" button with correct text for multiple changes', async () => {
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/setup/?check_write_access=true',
-      method: 'GET',
-      body: {
-        genAIConsent: {ok: true},
-        integration: {ok: true},
-        githubWriteIntegration: {
-          repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
-        },
-      },
-    });
-
-    const multipleChangesProps = {
-      ...changesStepProps,
-      step: {
-        ...changesStepProps.step,
-        changes: [AutofixCodebaseChangeData(), AutofixCodebaseChangeData()],
-      },
-    };
-
-    render(<AutofixMessageBox {...multipleChangesProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Use this code'}));
-
-    expect(screen.getByText('Push the above changes to 2 branches?')).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Draft PRs'})).toBeInTheDocument();
-  });
-
-  it('shows "View PR" buttons when PRs are created', () => {
-    render(<AutofixMessageBox {...prCreatedProps} />);
-
-    expect(screen.getByRole('button', {name: /View PR in/})).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: /View PR in/})).toHaveAttribute(
-      'href',
-      'https://github.com/owner/hello-world/pull/200'
-    );
-  });
-
-  it('shows multiple "View PR" buttons for multiple PRs', () => {
-    render(<AutofixMessageBox {...multiplePRsProps} />);
-
-    const viewPRButtons = screen.getAllByRole('button', {name: /View PR in/});
-    expect(viewPRButtons).toHaveLength(2);
-    expect(viewPRButtons[0]).toHaveAttribute(
-      'href',
-      'https://github.com/example/repo1/pull/1'
-    );
-    expect(viewPRButtons[1]).toHaveAttribute(
-      'href',
-      'https://github.com/example/repo2/pull/2'
-    );
-  });
-
-  it('shows "Draft PRs" button that opens setup modal when setup is incomplete', async () => {
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/setup/?check_write_access=true',
-      method: 'GET',
-      body: {
-        genAIConsent: {ok: true},
-        integration: {ok: true},
-        githubWriteIntegration: {
-          repos: [
-            {ok: false, provider: 'github', owner: 'owner', name: 'hello-world', id: 100},
-          ],
-        },
-      },
-    });
-    MockApiClient.addMockResponse({
-      url: '/issues/123/autofix/setup/',
-      method: 'GET',
-      body: {
-        genAIConsent: {ok: true},
-        integration: {ok: true},
-      },
-    });
-
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Use this code'}));
-
-    expect(screen.getByText('Push the above changes to a branch?')).toBeInTheDocument();
-
-    const createPRsButton = screen.getByRole('button', {name: 'Draft PR'});
-    expect(createPRsButton).toBeInTheDocument();
-
-    renderGlobalModal();
-    await userEvent.click(createPRsButton);
-
-    expect(await screen.findByRole('dialog')).toBeInTheDocument();
-    expect(
-      within(screen.getByRole('dialog')).getByText('Allow Autofix to Make Pull Requests')
-    ).toBeInTheDocument();
-  });
-
-  it('shows option buttons for changes step', () => {
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    expect(screen.getByRole('button', {name: 'Use this code'})).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Iterate'})).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Add tests'})).toBeInTheDocument();
-  });
-
-  it('shows "Test" button and static message when "Test" is selected', async () => {
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Add tests'}));
-
-    expect(
-      screen.getByText('Write unit tests to make sure the issue is fixed?')
-    ).toBeInTheDocument();
-    expect(screen.getByRole('button', {name: 'Add Tests'})).toBeInTheDocument();
-  });
-
-  it('sends correct message when "Test" is clicked without onSend prop', async () => {
-    MockApiClient.addMockResponse({
-      method: 'POST',
-      url: '/issues/123/autofix/update/',
-      body: {},
-    });
-
-    render(<AutofixMessageBox {...changesStepProps} />);
-
-    await userEvent.click(screen.getByRole('button', {name: 'Add tests'}));
-    await userEvent.click(screen.getByRole('button', {name: 'Add Tests'}));
-
-    await waitFor(() => {
-      expect(addSuccessMessage).toHaveBeenCalledWith('Thanks for the input.');
-    });
-  });
-});

Some files were not shown because too many files changed in this diff