| ... | @@ -34,7 +34,6 @@ import { | ... | @@ -34,7 +34,6 @@ import { |
| 34 | sortLogicalProjectsForSidebar, | 34 | sortLogicalProjectsForSidebar, |
| 35 | resolveSidebarDropTarget, | 35 | resolveSidebarDropTarget, |
| 36 | pinOrderKeyBetween, | 36 | pinOrderKeyBetween, |
| 37 | planPinnedReorder, | | |
| 38 | planSidebarThreadDrop, | 37 | planSidebarThreadDrop, |
| 39 | sidebarMarkerId, | 38 | sidebarMarkerId, |
| 40 | sidebarListItemId, | 39 | sidebarListItemId, |
| ... | @@ -958,59 +957,6 @@ describe("reduceSidebarProjectScopeMenuState", () => { | ... | @@ -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 | describe("pinOrderKeyBetween", () => { | 960 | describe("pinOrderKeyBetween", () => { |
| 1015 | it("produces keys that sort between their bounds", () => { | 961 | it("produces keys that sort between their bounds", () => { |
| 1016 | const middle = pinOrderKeyBetween(null, null)!; | 962 | const middle = pinOrderKeyBetween(null, null)!; |
| ... | @@ -1062,52 +1008,6 @@ describe("pinOrderKeyBetween", () => { | ... | @@ -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 | describe("resolveSidebarDropTarget", () => { | 1011 | describe("resolveSidebarDropTarget", () => { |
| 1112 | const thread = (key: string, section: SidebarSection): SidebarListItem => ({ | 1012 | const thread = (key: string, section: SidebarSection): SidebarListItem => ({ |
| 1113 | kind: "thread", | 1013 | kind: "thread", |