What I've been doing recently:

1. Vibe code a codebase that does what I want at a high level.

2. Iterate with LLM on this codebase to add features, fix bugs, improve performance, and address issues until it basically does what I want, but the code behind it is often a toxic waste dump.

3. Take lessons learned from vibe coded version and implement by hand. For challenging areas (writing a complicated algorithm) that would require a lot of thought or brainpower, I'll sometimes ask the LLM for a reference implementation and then modify it to suit.

This is a big speedup on manual code because you've figured out all the question-marks ahead of time and have a functioning blueprint you can refer to.

It's not as fast as having LLMs do all the code of course, but I find it to be a considerable improvement over doing everything by hand, while still letting me write code I'm comfortable with and understand deeply.

The other angle is to be very specific with the prompts and then dive deep into the code output and keep asking the LLM to change the code structure in various ways to ensure you get code you like. I found that to be frustrating and painful. Maybe in the future I'll write some really good prompts and future models will be better at following direction, but I haven't been happy with the results of that approach to date.