Skip to content

Commit bc3e4ab

Browse files
committed
fix(web): stop pinned threads moving after drop
1 parent 20a7042 commit bc3e4ab

3 files changed

Lines changed: 37 additions & 0 deletions

File tree

‎apps/web/src/components/Sidebar.logic.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
2+
import { defaultAnimateLayoutChanges, type AnimateLayoutChanges } from "@dnd-kit/sortable";
23
import {
4+
animatePinnedLayoutChanges,
35
archiveSelectedThreadEntries,
46
buildBulkTitleRegenerationContextMenuItem,
57
buildMultiSelectThreadContextMenuItems,
@@ -53,6 +55,32 @@ import {
5355

5456
const localEnvironmentId = EnvironmentId.make("environment-local");
5557

58+
describe("animatePinnedLayoutChanges", () => {
59+
const baseArgs: Parameters<AnimateLayoutChanges>[0] = {
60+
active: null,
61+
containerId: "pinned-threads",
62+
isDragging: false,
63+
isSorting: false,
64+
id: "thread-a",
65+
index: 1,
66+
items: ["thread-b", "thread-a"],
67+
newIndex: 0,
68+
previousItems: ["thread-a", "thread-b"],
69+
previousContainerId: "pinned-threads",
70+
transition: { duration: 200, easing: "ease" },
71+
wasDragging: true,
72+
};
73+
74+
it("does not replay layout movement after the pointer is released", () => {
75+
expect(defaultAnimateLayoutChanges(baseArgs)).toBe(true);
76+
expect(animatePinnedLayoutChanges(baseArgs)).toBe(false);
77+
});
78+
79+
it("keeps layout movement while the user is sorting", () => {
80+
expect(animatePinnedLayoutChanges({ ...baseArgs, isSorting: true })).toBe(true);
81+
});
82+
});
83+
5684
describe("shouldNavigateAfterProjectRemoval", () => {
5785
const projectThreads = [{ environmentId: "environment-local", id: "thread-1" }];
5886

‎apps/web/src/components/Sidebar.logic.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from "react";
2+
import { defaultAnimateLayoutChanges, type AnimateLayoutChanges } from "@dnd-kit/sortable";
23
import type { ContextMenuItem } from "@t3tools/contracts";
34
import type { SidebarProjectSortOrder, SidebarThreadSortOrder } from "@t3tools/contracts/settings";
45
import {
@@ -23,6 +24,12 @@ export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100;
2324
// it small; cold opens still render instantly from the cached snapshot.
2425
export const SIDEBAR_THREAD_PREWARM_LIMIT = 3;
2526

27+
// The list already reaches its destination through sortable transforms while
28+
// the pointer is down. dnd-kit's default also animates the committed DOM order
29+
// after release, replaying the same movement across every affected row.
30+
export const animatePinnedLayoutChanges: AnimateLayoutChanges = (args) =>
31+
args.isSorting ? defaultAnimateLayoutChanges(args) : false;
32+
2633
type SidebarProject = {
2734
id: string;
2835
title: string;

‎apps/web/src/components/Sidebar.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ import type { SidebarThreadSummary } from "../types";
121121
import { cn } from "~/lib/utils";
122122
import { buildThreadActionMenuItems } from "./threadActionMenu.logic";
123123
import {
124+
animatePinnedLayoutChanges,
124125
buildBulkTitleRegenerationContextMenuItem,
125126
formatWorkingDurationLabel,
126127
firstValidTimestampMs,
@@ -448,6 +449,7 @@ function SortablePinnedThreadRow(props: {
448449
}) {
449450
const { listeners, setNodeRef, transform, transition, isDragging } = useSortable({
450451
id: props.id,
452+
animateLayoutChanges: animatePinnedLayoutChanges,
451453
});
452454
return props.children({ listeners, setNodeRef, transform, transition, isDragging });
453455
}

0 commit comments

Comments
 (0)