diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx
index 331d71ca..c3b5a582 100644
--- a/ui/src/pages/IssueDetail.test.tsx
+++ b/ui/src/pages/IssueDetail.test.tsx
@@ -59,6 +59,7 @@ const mockProjectsApi = vi.hoisted(() => ({
const mockInstanceSettingsApi = vi.hoisted(() => ({
getGeneral: vi.fn(),
+ getExperimental: vi.fn(),
}));
const mockNavigate = vi.hoisted(() => vi.fn());
@@ -192,6 +193,7 @@ vi.mock("../components/InlineEditor", () => ({
vi.mock("../components/IssueChatThread", () => ({
IssueChatThread: (props: {
+ newestFirst?: boolean;
onWorkModeChange?: (workMode: string) => void;
issueWorkMode?: string;
onStopRun?: (runId: string) => Promise;
@@ -804,6 +806,9 @@ describe("IssueDetail", () => {
keyboardShortcuts: false,
feedbackDataSharingPreference: "prompt",
});
+ mockInstanceSettingsApi.getExperimental.mockResolvedValue({
+ enableNewestFirstIssueThread: false,
+ });
mockIssuesListRender.mockClear();
mockIssueChatThreadRender.mockClear();
});
@@ -839,6 +844,45 @@ describe("IssueDetail", () => {
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
+ it("passes oldest-first thread mode when the experimental flag is disabled", async () => {
+ mockIssuesApi.get.mockResolvedValue(createIssue());
+
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
+
+ await flushReact();
+
+ expect(mockIssueChatThreadRender.mock.calls.at(-1)?.[0]).toMatchObject({
+ newestFirst: false,
+ });
+ });
+
+ it("passes newest-first thread mode when the experimental flag is enabled", async () => {
+ mockIssuesApi.get.mockResolvedValue(createIssue());
+ mockInstanceSettingsApi.getExperimental.mockResolvedValue({
+ enableNewestFirstIssueThread: true,
+ });
+
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
+
+ await flushReact();
+
+ expect(mockIssueChatThreadRender.mock.calls.at(-1)?.[0]).toMatchObject({
+ newestFirst: true,
+ });
+ });
+
it("passes blocker attention to the issue detail header status icon", async () => {
mockIssuesApi.get.mockResolvedValue(createIssue({
status: "blocked",
diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx
index 3663b4c8..c859be20 100644
--- a/ui/src/pages/IssueDetail.tsx
+++ b/ui/src/pages/IssueDetail.tsx
@@ -592,6 +592,7 @@ type IssueDetailChatTabProps = {
issueId: string;
companyId: string;
projectId: string | null;
+ newestFirstIssueThreadEnabled: boolean;
issueStatus: Issue["status"];
issueWorkMode: IssueWorkMode;
executionRunId: string | null;
@@ -655,6 +656,7 @@ const IssueDetailChatTab = memo(function IssueDetailChatTab({
issueId,
companyId,
projectId,
+ newestFirstIssueThreadEnabled,
issueWorkMode,
issueStatus,
executionRunId,
@@ -855,6 +857,7 @@ const IssueDetailChatTab = memo(function IssueDetailChatTab({
) : null}
0 || resolvedHasActiveRun;
+ const { data: experimentalSettings } = useQuery({
+ queryKey: queryKeys.instance.experimentalSettings,
+ queryFn: () => instanceSettingsApi.getExperimental(),
+ placeholderData: keepPreviousDataForSameQueryTail(issueId ?? "pending"),
+ });
+ const newestFirstIssueThreadEnabled = experimentalSettings?.enableNewestFirstIssueThread === true;
useEffect(() => {
if (!hasLiveRuns && locallyQueuedCommentRunIds.size > 0) {
setLocallyQueuedCommentRunIds(new Map());
@@ -3781,6 +3790,7 @@ export function IssueDetail() {
issueId={issue.id}
companyId={issue.companyId}
projectId={issue.projectId ?? null}
+ newestFirstIssueThreadEnabled={newestFirstIssueThreadEnabled}
issueStatus={issue.status}
issueWorkMode={issue.workMode ?? "standard"}
executionRunId={issue.executionRunId ?? null}