I agree, the whole signed vs. unsigned generally feels like an afterthought in C (probably because, to a certain extent, it was). `char`'s sign being implementation-defined is a pretty wild design choice that wasted many hours of my life while porting code between ARM and x86.
UBs are not required, but you need them if you want C to behave as a macro-assembler as well as allowing for aggressive optimizations. For instance `a << b` if b is greater than a's width is genuinely UB if you write portable code, different CPUs will do different things in this situation. Defining the behaviour means that the compiler would have to insert additional opcodes to make the behaviour identical on all platforms.
You may argue that it's still better than having UB but that's just not C's design philosophy, for better or worse.