diff --git a/src/components/StrategyTimeline.tsx b/src/components/StrategyTimeline.tsx
index 25ad963d..d0d01ddf 100644
--- a/src/components/StrategyTimeline.tsx
+++ b/src/components/StrategyTimeline.tsx
@@ -31,6 +31,14 @@ type StrategyTimelineProps = {
compact?: boolean;
};
+function parseTimestamp(value?: string | null) {
+ if (!value) return null;
+ const hasTimezone = /Z|[+-]\d{2}:?\d{2}$/.test(value);
+ const normalized = hasTimezone ? value : `${value}Z`;
+ const parsed = new Date(normalized);
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
+}
+
function normalizeLog(log: unknown): StrategyEvent {
if (typeof log === "string") {
try {
@@ -49,25 +57,28 @@ function normalizeLog(log: unknown): StrategyEvent {
return { message: "Unknown log entry" };
}
-function formatTimestamp(value?: string) {
+function formatTimestamp(value?: string | null) {
if (!value) return "Unknown time";
- const hasTimezone = /Z|[+-]\d{2}:?\d{2}$/.test(value);
- const normalized = hasTimezone ? value : `${value}Z`;
- const parsed = new Date(normalized);
- if (Number.isNaN(parsed.getTime())) return value;
+ const parsed = parseTimestamp(value);
+ if (!parsed) return value;
return parsed.toLocaleString();
}
function formatCompactTimestamp(value?: string | null) {
- if (!value) return null;
- const parsed = new Date(value);
- if (Number.isNaN(parsed.getTime())) return null;
- return parsed.toLocaleTimeString([], {
+ const parsed = parseTimestamp(value);
+ if (!parsed) return null;
+ return parsed.toLocaleString([], {
+ month: "short",
+ day: "numeric",
hour: "numeric",
minute: "2-digit",
});
}
+function getTimestampMs(value?: string | null) {
+ return parseTimestamp(value)?.getTime() ?? 0;
+}
+
function getRunId(entry: StrategyEvent) {
return entry.run_id ?? "unknown";
}
@@ -178,9 +189,7 @@ function CompactStrategySummary() {
{message}
- {updatedAt ? (
- Updated {updatedAt}
- ) : null}
+ {updatedAt ? Updated {updatedAt} : null}
);
@@ -228,25 +237,34 @@ function VerboseStrategyTimeline() {
}, []);
const groupedRuns = useMemo(() => {
- const indexed = logs.map((entry, index) => ({ entry, index }));
- indexed.sort((a, b) => {
- const runCompare = getRunId(b.entry).localeCompare(getRunId(a.entry));
- if (runCompare !== 0) return runCompare;
- const seqCompare = getSeq(a.entry) - getSeq(b.entry);
+ const grouped = new Map();
+ for (const entry of logs) {
+ const runId = getRunId(entry);
+ const events = grouped.get(runId) ?? [];
+ events.push(entry);
+ grouped.set(runId, events);
+ }
+
+ const groups = Array.from(grouped.entries()).map(([runId, events]) => ({
+ runId,
+ events: [...events].sort((a, b) => {
+ const seqCompare = getSeq(a) - getSeq(b);
+ if (seqCompare !== 0) return seqCompare;
+ return getTimestampMs(a.ts ?? a.timestamp) - getTimestampMs(b.ts ?? b.timestamp);
+ }),
+ }));
+
+ groups.sort((a, b) => {
+ const aLast = a.events[a.events.length - 1];
+ const bLast = b.events[b.events.length - 1];
+ const seqCompare = getSeq(bLast) - getSeq(aLast);
if (seqCompare !== 0) return seqCompare;
- return a.index - b.index;
+ const timeCompare =
+ getTimestampMs(bLast.ts ?? bLast.timestamp) - getTimestampMs(aLast.ts ?? aLast.timestamp);
+ if (timeCompare !== 0) return timeCompare;
+ return b.runId.localeCompare(a.runId);
});
- const groups: StrategyRun[] = [];
- for (const { entry } of indexed) {
- const runId = getRunId(entry);
- const last = groups[groups.length - 1];
- if (!last || last.runId !== runId) {
- groups.push({ runId, events: [entry] });
- } else {
- last.events.push(entry);
- }
- }
return groups;
}, [logs]);
@@ -316,7 +334,7 @@ function VerboseStrategyTimeline() {
- {formatTimestamp(timestamp)} • {entry.event ?? "UNKNOWN"}
+ {formatTimestamp(timestamp)} - {entry.event ?? "UNKNOWN"}
{message && message !== entry.event ? (
{message}