Skip to content

Commit 0e1a582

Browse files
authored
Revert "Add experimental newest-first issue thread" (paperclipai#5460)
This is actually bad. Glad it was under experiments.
1 parent a904eff commit 0e1a582

9 files changed

Lines changed: 107 additions & 416 deletions

File tree

packages/shared/src/types/instance.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export interface InstanceGeneralSettings {
2929
export interface InstanceExperimentalSettings {
3030
enableEnvironments: boolean;
3131
enableIsolatedWorkspaces: boolean;
32-
enableNewestFirstIssueThread: boolean;
3332
autoRestartDevServerWhenIdle: boolean;
3433
enableIssueGraphLivenessAutoRecovery: boolean;
3534
issueGraphLivenessAutoRecoveryLookbackHours: number;

packages/shared/src/validators/instance.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export const patchInstanceGeneralSettingsSchema = instanceGeneralSettingsSchema.
3838
export const instanceExperimentalSettingsSchema = z.object({
3939
enableEnvironments: z.boolean().default(false),
4040
enableIsolatedWorkspaces: z.boolean().default(false),
41-
enableNewestFirstIssueThread: z.boolean().default(false),
4241
autoRestartDevServerWhenIdle: z.boolean().default(false),
4342
enableIssueGraphLivenessAutoRecovery: z.boolean().default(false),
4443
issueGraphLivenessAutoRecoveryLookbackHours: z

server/src/__tests__/instance-settings-routes.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ describe("instance settings routes", () => {
6464
mockInstanceSettingsService.getExperimental.mockResolvedValue({
6565
enableEnvironments: false,
6666
enableIsolatedWorkspaces: false,
67-
enableNewestFirstIssueThread: false,
6867
autoRestartDevServerWhenIdle: false,
6968
enableIssueGraphLivenessAutoRecovery: true,
7069
issueGraphLivenessAutoRecoveryLookbackHours: 24,
@@ -82,7 +81,6 @@ describe("instance settings routes", () => {
8281
experimental: {
8382
enableEnvironments: true,
8483
enableIsolatedWorkspaces: true,
85-
enableNewestFirstIssueThread: false,
8684
autoRestartDevServerWhenIdle: false,
8785
enableIssueGraphLivenessAutoRecovery: true,
8886
issueGraphLivenessAutoRecoveryLookbackHours: 24,
@@ -125,7 +123,6 @@ describe("instance settings routes", () => {
125123
expect(getRes.body).toEqual({
126124
enableEnvironments: false,
127125
enableIsolatedWorkspaces: false,
128-
enableNewestFirstIssueThread: false,
129126
autoRestartDevServerWhenIdle: false,
130127
enableIssueGraphLivenessAutoRecovery: true,
131128
issueGraphLivenessAutoRecoveryLookbackHours: 24,

server/src/services/instance-settings.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ function normalizeExperimentalSettings(raw: unknown): InstanceExperimentalSettin
4141
return {
4242
enableEnvironments: parsed.data.enableEnvironments ?? false,
4343
enableIsolatedWorkspaces: parsed.data.enableIsolatedWorkspaces ?? false,
44-
enableNewestFirstIssueThread: parsed.data.enableNewestFirstIssueThread ?? false,
4544
autoRestartDevServerWhenIdle: parsed.data.autoRestartDevServerWhenIdle ?? false,
4645
enableIssueGraphLivenessAutoRecovery: parsed.data.enableIssueGraphLivenessAutoRecovery ?? false,
4746
issueGraphLivenessAutoRecoveryLookbackHours:
@@ -52,7 +51,6 @@ function normalizeExperimentalSettings(raw: unknown): InstanceExperimentalSettin
5251
return {
5352
enableEnvironments: false,
5453
enableIsolatedWorkspaces: false,
55-
enableNewestFirstIssueThread: false,
5654
autoRestartDevServerWhenIdle: false,
5755
enableIssueGraphLivenessAutoRecovery: false,
5856
issueGraphLivenessAutoRecoveryLookbackHours:

ui/src/components/IssueChatThread.test.tsx

Lines changed: 2 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -295,19 +295,16 @@ function createFileDragEvent(type: string, files: File[]) {
295295

296296
describe("IssueChatThread", () => {
297297
let container: HTMLDivElement;
298-
const originalDocumentElementScrollIntoView = document.documentElement.scrollIntoView;
299298

300299
beforeEach(() => {
301300
container = document.createElement("div");
302301
document.body.appendChild(container);
303302
window.scrollTo = vi.fn();
304-
document.documentElement.scrollIntoView = vi.fn() as unknown as typeof document.documentElement.scrollIntoView;
305303
localStorage.clear();
306304
});
307305

308306
afterEach(() => {
309307
container.remove();
310-
document.documentElement.scrollIntoView = originalDocumentElementScrollIntoView;
311308
vi.useRealTimers();
312309
appendMock.mockReset();
313310
markdownEditorFocusMock.mockReset();
@@ -330,7 +327,6 @@ describe("IssueChatThread", () => {
330327
liveRuns={[]}
331328
onAdd={async () => {}}
332329
showComposer={false}
333-
newestFirst
334330
enableLiveTranscriptPolling={false}
335331
/>
336332
</MemoryRouter>,
@@ -340,16 +336,6 @@ describe("IssueChatThread", () => {
340336
expect(container.textContent).toContain("Jump to latest");
341337
expect(container.textContent).not.toContain("Chat (");
342338

343-
const threadRoot = container.querySelector('[data-testid="thread-root"]');
344-
const jumpButton = Array.from(container.querySelectorAll("button")).find(
345-
(button) => button.textContent === "Jump to latest",
346-
);
347-
expect(threadRoot).not.toBeNull();
348-
expect(jumpButton).toBeDefined();
349-
expect(
350-
threadRoot?.compareDocumentPosition(jumpButton!),
351-
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
352-
353339
const viewport = container.querySelector('[data-testid="thread-viewport"]') as HTMLDivElement | null;
354340
expect(viewport).not.toBeNull();
355341
expect(viewport?.className).not.toContain("overflow-y-auto");
@@ -360,106 +346,6 @@ describe("IssueChatThread", () => {
360346
});
361347
});
362348

363-
it("defaults to oldest-first rendering and jump placement", () => {
364-
const root = createRoot(container);
365-
366-
act(() => {
367-
root.render(
368-
<MemoryRouter>
369-
<IssueChatThread
370-
comments={[
371-
{
372-
id: "comment-older",
373-
companyId: "company-1",
374-
issueId: "issue-1",
375-
authorAgentId: "agent-1",
376-
authorUserId: null,
377-
body: "Older comment",
378-
authorType: "agent",
379-
presentation: null,
380-
metadata: null,
381-
createdAt: new Date("2026-04-06T12:00:00.000Z"),
382-
updatedAt: new Date("2026-04-06T12:00:00.000Z"),
383-
},
384-
{
385-
id: "comment-newer",
386-
companyId: "company-1",
387-
issueId: "issue-1",
388-
authorAgentId: "agent-1",
389-
authorUserId: null,
390-
body: "Newer comment",
391-
authorType: "agent",
392-
presentation: null,
393-
metadata: null,
394-
createdAt: new Date("2026-04-06T12:01:00.000Z"),
395-
updatedAt: new Date("2026-04-06T12:01:00.000Z"),
396-
},
397-
]}
398-
linkedRuns={[]}
399-
timelineEvents={[]}
400-
liveRuns={[]}
401-
onAdd={async () => {}}
402-
showComposer={false}
403-
enableLiveTranscriptPolling={false}
404-
/>
405-
</MemoryRouter>,
406-
);
407-
});
408-
409-
const rows = Array.from(container.querySelectorAll('[data-testid="issue-chat-message-row"]'));
410-
expect(rows[0]?.textContent).toContain("Older comment");
411-
expect(rows[1]?.textContent).toContain("Newer comment");
412-
413-
const threadRoot = container.querySelector('[data-testid="thread-root"]');
414-
const jumpButton = Array.from(container.querySelectorAll("button")).find(
415-
(button) => button.textContent === "Jump to latest",
416-
);
417-
expect(threadRoot).not.toBeNull();
418-
expect(jumpButton).toBeDefined();
419-
expect(
420-
threadRoot?.compareDocumentPosition(jumpButton!),
421-
).toBe(Node.DOCUMENT_POSITION_PRECEDING);
422-
423-
act(() => {
424-
root.unmount();
425-
});
426-
});
427-
428-
it("renders the jump control above the thread when newest-first mode is disabled", () => {
429-
const root = createRoot(container);
430-
431-
act(() => {
432-
root.render(
433-
<MemoryRouter>
434-
<IssueChatThread
435-
comments={[]}
436-
linkedRuns={[]}
437-
timelineEvents={[]}
438-
liveRuns={[]}
439-
onAdd={async () => {}}
440-
showComposer={false}
441-
newestFirst={false}
442-
enableLiveTranscriptPolling={false}
443-
/>
444-
</MemoryRouter>,
445-
);
446-
});
447-
448-
const threadRoot = container.querySelector('[data-testid="thread-root"]');
449-
const jumpButton = Array.from(container.querySelectorAll("button")).find(
450-
(button) => button.textContent === "Jump to latest",
451-
);
452-
expect(threadRoot).not.toBeNull();
453-
expect(jumpButton).toBeDefined();
454-
expect(
455-
threadRoot?.compareDocumentPosition(jumpButton!),
456-
).toBe(Node.DOCUMENT_POSITION_PRECEDING);
457-
458-
act(() => {
459-
root.unmount();
460-
});
461-
});
462-
463349
it("renders the composer in planning mode when the issue is in planning mode", () => {
464350
const root = createRoot(container);
465351

@@ -1073,7 +959,6 @@ describe("IssueChatThread", () => {
1073959
agentMap={issueChatLongThreadAgentMap}
1074960
currentUserId="user-board"
1075961
onAdd={async () => {}}
1076-
newestFirst
1077962
enableLiveTranscriptPolling={false}
1078963
onRefreshLatestComments={async () => {
1079964
setComments([olderComment, latestComment]);
@@ -1110,15 +995,15 @@ describe("IssueChatThread", () => {
1110995
});
1111996
});
1112997

1113-
it("findLatestCommentMessageIndex prefers the first comment-anchored row when newest renders first", () => {
998+
it("findLatestCommentMessageIndex prefers the last comment-anchored row (PAP-2672)", () => {
1114999
const messages = [
11151000
{ metadata: { custom: { anchorId: "comment-a" } } },
11161001
{ metadata: { custom: { anchorId: "run-1" } } },
11171002
{ metadata: { custom: { anchorId: "comment-b" } } },
11181003
{ metadata: { custom: { anchorId: "run-2" } } },
11191004
{ metadata: { custom: { anchorId: "activity-3" } } },
11201005
];
1121-
expect(findLatestCommentMessageIndex(messages as never)).toBe(0);
1006+
expect(findLatestCommentMessageIndex(messages as never)).toBe(2);
11221007
expect(
11231008
findLatestCommentMessageIndex([
11241009
{ metadata: { custom: { anchorId: "run-only" } } },
@@ -1127,17 +1012,6 @@ describe("IssueChatThread", () => {
11271012
expect(findLatestCommentMessageIndex([] as never)).toBe(-1);
11281013
});
11291014

1130-
it("findLatestCommentMessageIndex prefers the last comment-anchored row when newest-first mode is disabled", () => {
1131-
const messages = [
1132-
{ metadata: { custom: { anchorId: "comment-a" } } },
1133-
{ metadata: { custom: { anchorId: "run-1" } } },
1134-
{ metadata: { custom: { anchorId: "comment-b" } } },
1135-
{ metadata: { custom: { anchorId: "run-2" } } },
1136-
{ metadata: { custom: { anchorId: "activity-3" } } },
1137-
];
1138-
expect(findLatestCommentMessageIndex(messages as never, false)).toBe(2);
1139-
});
1140-
11411015
it("keeps the direct render path for short threads under the virtualization threshold", () => {
11421016
const root = createRoot(container);
11431017
const directComments = issueChatLongThreadComments.slice(0, 12);
@@ -1846,50 +1720,6 @@ describe("IssueChatThread", () => {
18461720
});
18471721
});
18481722

1849-
it("renders the comment timestamp above the comment body", () => {
1850-
vi.useFakeTimers();
1851-
vi.setSystemTime(new Date("2026-04-08T12:00:00.000Z"));
1852-
const root = createRoot(container);
1853-
1854-
act(() => {
1855-
root.render(
1856-
<MemoryRouter>
1857-
<IssueChatThread
1858-
comments={[{
1859-
id: "comment-1",
1860-
companyId: "company-1",
1861-
issueId: "issue-1",
1862-
authorAgentId: "agent-1",
1863-
authorUserId: null,
1864-
body: "Agent summary",
1865-
authorType: "agent",
1866-
presentation: null,
1867-
metadata: null,
1868-
createdAt: new Date("2026-04-06T12:00:00.000Z"),
1869-
updatedAt: new Date("2026-04-06T12:00:00.000Z"),
1870-
}]}
1871-
linkedRuns={[]}
1872-
timelineEvents={[]}
1873-
liveRuns={[]}
1874-
onAdd={async () => {}}
1875-
showComposer={false}
1876-
newestFirst
1877-
enableLiveTranscriptPolling={false}
1878-
/>
1879-
</MemoryRouter>,
1880-
);
1881-
});
1882-
1883-
const text = container.textContent ?? "";
1884-
const timestampIndex = text.indexOf("2d ago");
1885-
expect(timestampIndex).toBeGreaterThanOrEqual(0);
1886-
expect(timestampIndex).toBeLessThan(text.indexOf("Agent summary"));
1887-
1888-
act(() => {
1889-
root.unmount();
1890-
});
1891-
});
1892-
18931723
it("shows deferred wake badge only for hold-deferred queued comments", () => {
18941724
const root = createRoot(container);
18951725

0 commit comments

Comments
 (0)