Why EXPLAIN doesn't show heap fetches for index scan
Index scans fetch from the heap for every single row.  That is what makes it not be an index-only scan.  It only makes sense to display the count for index-only scans, as that is the only case in which it is informative.

The line "Buffers:" line may be more informative in general (for a realistic case where you have more than one row at stake).  But for your IOS that line shows 4 in both cases.  The reason that that doesn't change is that before vacuuming, the table doesn't have a visibility map, so there is no page to be read into a buffer.  It realizes the vm doesn't exist, an hits the table page instead.  After vacuuming, it hits the (now existing) vm page, which tells us doesn't need to hit the table.  So you always get 4 Buffers hit, 3 for the index and 1 for either the vm or the table.

If you were to partially populate the table, then vacuum so that the vm exists, then insert another record, and then query for that record, you would see that the IOS now hits 5 buffers.  3 for the index, one for the visibility map page which says the page is not all visible, and 1 for the table.

If you have large queries which hits the index many times, then interpreting the buffer hit count gets really tricky.  Within a single query execution, it keeps a pin on the most recently used visibility map page.  Hitting the same page repeatedly while you still hold a pin on it doesn't count as a new buffer hit.  Only if you need to switch to a different page in the vm does it count as a new buffer hit.

All of this unlikely to be very effective.  Make up a realistic data set of a realistic size on realistic hardware, and run realistic queries against it, and measure actual performance.  Once you have legitimate performance differences which need explaining, you can dig into the details to find the explanation.  But trying to extrapolate up from a one-row query is not very useful.