Good breakdown. One thing worth adding, because it catches people who use spread as a "copy" operator: {...obj} copies own enumerable properties only. The prototype is not carried over, so {...new Date()} gives you {} and {...someClassInstance} loses every method, since methods live on the prototype and not on the instance. Getters are evaluated at spread time and become plain values, and anything non-enumerable simply disappears.
And on the spread-as-arguments side: Math.max(...arr) is elegant right up until arr is large. Every element becomes a real argument, so on big arrays you hit RangeError: Maximum call stack size exceeded, and the exact threshold is engine-dependent rather than specified. arr.reduce((a, b) => Math.max(a, b)) is the boring version that never blows up.