| ... | ... | @@ -34,7 +34,6 @@ import { |
| 34 | 34 | sortLogicalProjectsForSidebar, |
| 35 | 35 | resolveSidebarDropTarget, |
| 36 | 36 | pinOrderKeyBetween, |
| 37 | | planPinnedReorder, |
| 38 | 37 | planSidebarThreadDrop, |
| 39 | 38 | sidebarMarkerId, |
| 40 | 39 | sidebarListItemId, |
| ... | ... | @@ -958,59 +957,6 @@ describe("reduceSidebarProjectScopeMenuState", () => { |
| 958 | 957 | }); |
| 959 | 958 | }); |
| 960 | 959 | |
| 961 | | describe("sortThreadsForSidebar", () => { |
| 962 | | const sortable = (input: { id: string; createdAt: string }) => ({ |
| 963 | | id: input.id, |
| 964 | | createdAt: input.createdAt, |
| 965 | | }); |
| 966 | | |
| 967 | | it("orders by creation time, newest first, ignoring activity", () => { |
| 968 | | const sorted = sortThreadsForSidebar([ |
| 969 | | sortable({ id: "oldest", createdAt: "2026-03-09T08:00:00.000Z" }), |
| 970 | | sortable({ id: "newest", createdAt: "2026-03-09T12:00:00.000Z" }), |
| 971 | | sortable({ id: "middle", createdAt: "2026-03-09T10:00:00.000Z" }), |
| 972 | | ]); |
| 973 | | |
| 974 | | expect(sorted.map((thread) => thread.id)).toEqual(["newest", "middle", "oldest"]); |
| 975 | | }); |
| 976 | | |
| 977 | | it("breaks creation-time ties by id so the order is stable", () => { |
| 978 | | const sorted = sortThreadsForSidebar([ |
| 979 | | sortable({ id: "b", createdAt: "2026-03-09T10:00:00.000Z" }), |
| 980 | | sortable({ id: "a", createdAt: "2026-03-09T10:00:00.000Z" }), |
| 981 | | ]); |
| 982 | | |
| 983 | | expect(sorted.map((thread) => thread.id)).toEqual(["a", "b"]); |
| 984 | | }); |
| 985 | | |
| 986 | | it("surfaces an un-settled thread at the top via its re-entry stamp", () => { |
| 987 | | const sorted = sortThreadsForSidebar([ |
| 988 | | { |
| 989 | | id: "old-unsettled", |
| 990 | | createdAt: "2026-03-09T08:00:00.000Z", |
| 991 | | unsettledAt: "2026-03-09T13:00:00.000Z", |
| 992 | | }, |
| 993 | | sortable({ id: "newest", createdAt: "2026-03-09T12:00:00.000Z" }), |
| 994 | | sortable({ id: "middle", createdAt: "2026-03-09T10:00:00.000Z" }), |
| 995 | | ]); |
| 996 | | |
| 997 | | expect(sorted.map((thread) => thread.id)).toEqual(["old-unsettled", "newest", "middle"]); |
| 998 | | }); |
| 999 | | |
| 1000 | | it("ignores a re-entry stamp older than the thread's creation", () => { |
| 1001 | | const sorted = sortThreadsForSidebar([ |
| 1002 | | { |
| 1003 | | id: "stale-stamp", |
| 1004 | | createdAt: "2026-03-09T10:00:00.000Z", |
| 1005 | | unsettledAt: "2026-03-09T09:00:00.000Z", |
| 1006 | | }, |
| 1007 | | sortable({ id: "newest", createdAt: "2026-03-09T12:00:00.000Z" }), |
| 1008 | | ]); |
| 1009 | | |
| 1010 | | expect(sorted.map((thread) => thread.id)).toEqual(["newest", "stale-stamp"]); |
| 1011 | | }); |
| 1012 | | }); |
| 1013 | | |
| 1014 | 960 | describe("pinOrderKeyBetween", () => { |
| 1015 | 961 | it("produces keys that sort between their bounds", () => { |
| 1016 | 962 | const middle = pinOrderKeyBetween(null, null)!; |
| ... | ... | @@ -1062,52 +1008,6 @@ describe("pinOrderKeyBetween", () => { |
| 1062 | 1008 | }); |
| 1063 | 1009 | }); |
| 1064 | 1010 | |
| 1065 | | describe("planPinnedReorder", () => { |
| 1066 | | it("writes only the moved thread when neighbors are keyed", () => { |
| 1067 | | const assignments = planPinnedReorder({ |
| 1068 | | orderedIds: ["a", "c", "b"], |
| 1069 | | keysById: new Map([ |
| 1070 | | ["a", "f"], |
| 1071 | | ["b", "m"], |
| 1072 | | ["c", "t"], |
| 1073 | | ]), |
| 1074 | | movedId: "c", |
| 1075 | | }); |
| 1076 | | expect(assignments).toHaveLength(1); |
| 1077 | | expect(assignments[0]!.id).toBe("c"); |
| 1078 | | expect(assignments[0]!.orderKey > "f" && assignments[0]!.orderKey < "m").toBe(true); |
| 1079 | | }); |
| 1080 | | |
| 1081 | | it("treats list edges as open bounds", () => { |
| 1082 | | const assignments = planPinnedReorder({ |
| 1083 | | orderedIds: ["b", "a"], |
| 1084 | | keysById: new Map([ |
| 1085 | | ["a", "m"], |
| 1086 | | ["b", null], |
| 1087 | | ]), |
| 1088 | | movedId: "b", |
| 1089 | | }); |
| 1090 | | expect(assignments).toHaveLength(1); |
| 1091 | | expect(assignments[0]!.orderKey < "m").toBe(true); |
| 1092 | | }); |
| 1093 | | |
| 1094 | | it("materializes keys for the whole section when a neighbor is keyless", () => { |
| 1095 | | const assignments = planPinnedReorder({ |
| 1096 | | orderedIds: ["b", "a", "c"], |
| 1097 | | keysById: new Map([ |
| 1098 | | ["a", null], |
| 1099 | | ["b", "m"], |
| 1100 | | ["c", null], |
| 1101 | | ]), |
| 1102 | | movedId: "b", |
| 1103 | | }); |
| 1104 | | expect(assignments.map((entry) => entry.id)).toEqual(["b", "a", "c"]); |
| 1105 | | const keys = assignments.map((entry) => entry.orderKey); |
| 1106 | | expect([...keys].sort()).toEqual(keys); |
| 1107 | | expect(new Set(keys).size).toBe(keys.length); |
| 1108 | | }); |
| 1109 | | }); |
| 1110 | | |
| 1111 | 1011 | describe("resolveSidebarDropTarget", () => { |
| 1112 | 1012 | const thread = (key: string, section: SidebarSection): SidebarListItem => ({ |
| 1113 | 1013 | kind: "thread", |