You cannot (or should not, I forget) really use Knockout in an imperative way like that, where you manually do or do not generate opening/closing tags. Instead, you need to nest things properly.
Put differently, treat <!-- ko if: ... -->
and <!-- /ko -->
as proper close/end tags that always need to wrap around full elements.
Put even differently, you're trying to do the equivalent of this:
<div>
Some content
<strong>
<div>
</strong>
CONTENT
<strong>
</div>
</strong>
</p>
Perhaps Knockout should've given you an error, but (I guess in the spirit of HTML) it tries to make the best of it.
So instead, you should do the "grouping" logic in your view model. Added benefit is that you could potentially unit test it too. Here's an example:
const chunkSize = 3;
class RootVm {
displaySel = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
displaySelGroups = ko.computed(() =>
// Chunk in groups of 3 based on:
// https://stackoverflow.com/a/44687374/419956
[...Array(Math.ceil(this.displaySel.length / chunkSize))].map(_ => this.displaySel.splice(0,chunkSize))
);
}
ko.applyBindings(new RootVm());
.col-2 { background: silver; border: 1px solid black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css" integrity="sha512-P5MgMn1jBN01asBgU0z60Qk4QxiXo86+wlFahKrsQf37c9cro517WzVSPPV1tDKzhku2iJ2FVgL67wG03SGnNA==" crossorigin="anonymous" />
<div class="row" data-bind="foreach: displaySelGroups">
<div class="col-2" data-bind="foreach: $data">
<div data-bind="attr:{id: 'div_'+$index()}">
<div data-bind="attr:{id: 'g_'+$data.hId}" style="position:relative;">
<div data-bind="text: $data"></div>
</div>
</div>
</div>
</div>