diff --git a/ui/src/ui/views/dreaming.test.ts b/ui/src/ui/views/dreaming.test.ts index 3e75fb033da..385a367812a 100644 --- a/ui/src/ui/views/dreaming.test.ts +++ b/ui/src/ui/views/dreaming.test.ts @@ -409,5 +409,68 @@ describe("dreaming view", () => { setDreamSubTab("scene"); }); + it("treats malformed waiting-entry timestamps as oldest in both sort modes", () => { + setDreamSubTab("advanced"); + const shortTermEntries = [ + { + key: "memory:valid-recent", + path: "memory/2026-04-06.md", + startLine: 1, + endLine: 1, + snippet: "Valid recent timestamp", + recallCount: 1, + dailyCount: 0, + groundedCount: 0, + totalSignalCount: 3, + lightHits: 1, + remHits: 0, + phaseHitCount: 1, + lastRecalledAt: "2026-04-06T12:00:00.000Z", + }, + { + key: "memory:malformed-time", + path: "memory/2026-04-05.md", + startLine: 1, + endLine: 1, + snippet: "Malformed timestamp", + recallCount: 1, + dailyCount: 0, + groundedCount: 0, + totalSignalCount: 3, + lightHits: 1, + remHits: 0, + phaseHitCount: 1, + lastRecalledAt: "not-a-timestamp", + }, + ]; + + setDreamAdvancedWaitingSort("recent"); + let container = renderInto( + buildProps({ + shortTermEntries, + promotedEntries: [], + }), + ); + const recentOrder = [...container.querySelectorAll("[data-entry-key]")].map((node) => + node.getAttribute("data-entry-key"), + ); + expect(recentOrder).toEqual(["memory:valid-recent", "memory:malformed-time"]); + + setDreamAdvancedWaitingSort("signals"); + container = renderInto( + buildProps({ + shortTermEntries, + promotedEntries: [], + }), + ); + const signalOrder = [...container.querySelectorAll("[data-entry-key]")].map((node) => + node.getAttribute("data-entry-key"), + ); + expect(signalOrder).toEqual(["memory:valid-recent", "memory:malformed-time"]); + + setDreamAdvancedWaitingSort("recent"); + setDreamSubTab("scene"); + }); + // Toggle lives in the page header (app-render.ts), not inside the dreaming view. }); diff --git a/ui/src/ui/views/dreaming.ts b/ui/src/ui/views/dreaming.ts index 1cda9f91ca2..b92a1cd2f94 100644 --- a/ui/src/ui/views/dreaming.ts +++ b/ui/src/ui/views/dreaming.ts @@ -430,13 +430,19 @@ function formatCompactDateTime(value: string): string { }); } +function parseSortableTimestamp(value?: string): number { + if (!value) { + return Number.NEGATIVE_INFINITY; + } + const parsed = Date.parse(value); + return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY; +} + function compareWaitingEntryByRecency(a: DreamingEntry, b: DreamingEntry): number { - const aMs = a.lastRecalledAt ? Date.parse(a.lastRecalledAt) : Number.NEGATIVE_INFINITY; - const bMs = b.lastRecalledAt ? Date.parse(b.lastRecalledAt) : Number.NEGATIVE_INFINITY; - if (Number.isFinite(aMs) || Number.isFinite(bMs)) { - if (bMs !== aMs) { - return bMs - aMs; - } + const aMs = parseSortableTimestamp(a.lastRecalledAt); + const bMs = parseSortableTimestamp(b.lastRecalledAt); + if (bMs !== aMs) { + return bMs - aMs; } if (b.totalSignalCount !== a.totalSignalCount) { return b.totalSignalCount - a.totalSignalCount;