```ts
export function spanMerge(spans: number[][]): number[][] {
  if (spans.length === 0) {
    return [];
  }

  // Sort spans by start time
  const sorted = [...spans].sort((a, b) => a[0] - b[0]);

  const merged: number[][] = [];
  let currentStart = sorted[0][0];
  let currentEnd = sorted[0][1];

  for (let i = 1; i < sorted.length; i++) {
    const nextStart = sorted[i][0];
    const nextEnd = sorted[i][1];

    // If the next span starts before or at the current end, they overlap or touch
    if (nextStart <= currentEnd) {
      currentEnd = Math.max(currentEnd, nextEnd);
    } else {
      merged.push([currentStart, currentEnd]);
      currentStart = nextStart;
      currentEnd = nextEnd;
    }
  }

  merged.push([currentStart, currentEnd]);
  return merged;
}
```