I'd say it depends how complex your instances get, because tools have their sweet spots. In my simple view, TF is mainly for managing cloud resources (as black boxes) but can do a bit more; while other tools are mainly for managing the "contents" of those resources, once created. For machines that's a clear difference, for other types of resources it's more of a grey area.
I've used a lot of puppet over the years (after the instances got created with terraform, with a bit of cloud-init to install puppet and trigger a first run). For "complex" machines (lots of packages / config file changes) it's nice to have a tool that provides a bit of abstraction and re-use. Puppet, Ansible, Chef could all do this and is a matter of taste, and what best suits the (experience of the) team maintaining it. For machines that only have trivial configuration one could stick with terraform's remote-exec provisioner (fine for idempotent actions) and use the null resource to make it remember in its state file that it already did a particular action once (for non-idempotent actions). And when possible, rebuilding machines over maintaining them for a long time also saves on patching effort. We've always ensured that provisioning machines (both the machine creation and their config mgmt) was fully automatic and unattended, but sometimes that process is too slow to do regularly (i.e. if it takes puppet an > 30 mins to download and install all the relevant things because we only need a tiny instance type, then it may not be convenient to replace machines e.g. weekly).
We started to use Kubernetes more in the last years; and also there we found the practical limitation of Terraform. We do use the helm provider a little bit to bootstrap new clusters, but to manage all app deployments after that we didn't think it was convenient enough, so we use more fit-for-purpose tools once a cluster is running.