tty: fix progress bar rendering on narrow terminals
Root cause: terminal_width() returned 80 (fallback) instead of the
actual width because tput/COLUMNS were unreliable. An 80-char progress
line on a 71-column terminal wraps, and \r only returns to the start
of the wrapped portion — each update appears on a new line.
Fix: use ioctl TIOCGWINSZ via C stub for reliable width detection.
Also: all progress output now goes through Format.pp (no split between
Format and output_string stdout), matching CraigFe's progress library
pattern. Tests use Format.str_formatter + flush_str_formatter.
Additional fixes:
- Progress.suspend: clear bar, run function, redraw (for interleaving)
- Progress.logs_reporter: wraps Logs reporter to auto-suspend progress
- Vlog wires up logs_reporter so Log.app doesn't break the progress bar
- Progress.message/set_phase no longer render (batch until next tick)