that's the conflict, between the "main is the release" and "main is the unstable development" because it's the most visible, if you only allow stable releases on it, the activity you see on the main page of the repo is very slow and seems like nothing is happening
building in public is a key part of the social engagement of open source development and for that reason, the consensus is moving towards unstable mains, and forget main, call it "dev" or "development" so it's clearly unstable, and mark milestones on tags, which can be combined with "releases" when a minor or major version bump happens, build out binaries or whatever as the case may be, package APKs etc
the main branch SHOULD always build though, when i say development i don't mean unstable as in can be not working but unstable as in, may have bugs still until those are fixed and put into a tagged release with a version number
for reasons of how the Go toolchain looks for the newest release or newest tag, it's also important for developers with libraries because the tags are easy to remember and you can write them straight into the module file (like a package.lock, but sorta like a package.json but it's neither)... anyway, the same thing can be applied, not so automatically, with other languages, but yeah
i agree that it should build on the main branch, i just don't agree that it should necessarily be working, and that this is what tagging is for
side branches make sense only with a very large project and teams of more than about 4-5 developers, and the first level of them usually will be platform targets more so than features... again it depends on the size of the team, two guys on a project mostly you can just work on a branch that's your branch and then merge them up to the development branch when they are building and kinda runing, and when the features are stable, you tag a minor version bump, otherwise, tagging patches for potentially unstable changes, so they can be easily referred to