◆ 外部ライブラリで直接 DOM を並び替えるのに対応したい
◆ DOM を変えたら render 時に使う配列も更新する
◆ 問題ないケースもあるけど問題あるケースもある

hyperhtml や lit-html を使うなら 基本はこれらのライブラリで要素の並び替えもするべきです
しかし色々あって それらとは外部の仕組みで DOM を並び替えることもあります
そのときにどうすれば問題なく使えるか調べてみました

試すためのページを用意しました
このページは hyperhtml を使ってます

<!doctype html>
<meta charset="utf-8"/>

<style>
.item {
background: #eef;
border: 1px solid #0003;
padding: 3px 10px;
margin: 3px;
}

</style>

<script type="module">
import { bind, wire } from "https://unpkg.com/hyperhtml/esm/index.js?module"

const items = []
let seq = 1

const add = () => {
const data = {
id: String(seq++).padStart(4, "0"),
}
items.push(data)
update()
}

const remove = eve => {
const idx = items.findIndex(item => item.id === eve.target.dataset.id)
if(idx >= 0) items.splice(idx, 1)
update()
}

const domShuffle1 = () => {
const elems = [...document.querySelectorAll(".item")]
const container = elems[0].parentElement
shuffleArray(items)
const sorted_elems = items.map(item => elems.find(elem => item.id === elem.dataset.id))
container.prepend(...sorted_elems)
update()
}

const domShuffle2 = () => {
const elems = [...document.querySelectorAll(".item")]
const container = elems[0].parentElement
swapEdge(items)
const sorted_elems = items.map(item => elems.find(elem => item.id === elem.dataset.id))
container.prepend(...sorted_elems)
update()
}

const shuffleArray = a => {
for(let i=0; i < a.length; i++) {
const j = ~~(Math.random() * a.length)
const t = a[i]
a[i] = a[j]
a[j] = t
}
}

const swapEdge = a => {
const b = a.pop()
const c = a.shift()
a.push(c)
a.unshift(b)
}

const render = bind(document.getElementById("root"))

const update = () => {
render`
<div>
<button onclick=${add}>+</button>
<button onclick=${domShuffle1}>Shuffle1</button>
<button onclick=${domShuffle2}>Shuffle2</button>
<div>
${
items.map(e => wire(e)`
<div class="item" ondblclick=${remove} data-id=${e.id}>${e.id}</div>
`)
}
</div>
</div>
`
const dom_ids = Array.from(document.querySelectorAll(".item"), e => e.dataset.id)
console.log(items.map(e => e.id), items.every((e, i) => e.id === dom_ids[i]))
}

for(let i = 0; i < 8; i++) add()
update()
</script>

<div id="root"></div>

items がオブジェクトの配列で それぞれのオブジェクトには id プロパティがあります
中身はただの連番で これを表示して DOM の見た目の並びがわかるようにしています

+ボタンで要素を追加して ダブルクリックで要素を削除できます
これらは items を更新して DOM は hyperhtml の機能で更新します

Shuffle1 はランダムに並び替えで Shuffle2 は最初と最後の入れ替えです
この並び替えは hyperhtml ではなく直接 DOM を操作して並び替えています

本来は DOM を更新してそれを items にも反映するのですが ここでは処理を楽にするために items を先に並び替えて DOM に反映しています
両方を並び替えているので items と DOM の並びは同じで hyperhtml では何も変更されないというのが期待する動作です

hyperhtml では Comment Node を使って場所を判定するので コメントとの位置関係が重要です
ソート後のものを prepend して Comment Node より前に来るようにして位置関係を保っています
append にすると Comment Node より後ろに来て順番が変わるのでおかしくなります

render 後に DOM と items の順番が一致するかを console に出力しています

結果

すでに使っていたいくつかのバージョンで試すと結果が違ったのでバージョンを色々試してみました

2.12.0

ランダムシャッフルだと基本問題なしです
ただし 4 つ以上の要素で両端の入れ替えだと false になりました

2.14.0

基本問題なかったのですが 要素が 2 つでそれらが入れ替わるとエラーが起きました

2.19.1 以降 (現最新版 2.32.1 まで)

2 要素での問題はなくなり 両端入れ替えやランダム入れ替えで特に問題は起きていません
しかし 削除を行うと問題がおきました
削除後に 3 つ以上の要素がある状態で両端入れ替えをすると false になりました
削除した時点の render で items と DOM は同期されてるわけなので次に何かしても削除前と同じようになりそうですが 削除後だけおかしくなります

lit-html

確認用ページを lit-html に置き換えて試してみました
最初のシャッフルからいきなり false でした

まとめ

DOM と items を揃えたところで ここも以前の状態のキャッシュを持ってるので余計な入れ替えが発生して不整合が起きます
lit-html の live が使えればよかったのですが live ディレクティブは属性・プロパティ part のみで node part では使えませんでした
hyperhtml だと wire に参照を持たせなければ新規作成になるので不整合は起きないようにできます
ただし 全要素を作り直すので 要素数や更新頻度によっては重くなります
並び替えボタンを押したときだけならともかく 別部分がタイマーで更新されていたらその更新も影響受けますからね

結局 外部ツールで並び替えるを避けるのがベストですが 避けられないなら DOM の変更をもとに戻した上で hyperhtml や lit-html の機能で更新するのが最善なのかもしれません
それもボタン押したときの同期的な処理のみならいいのですが アニメーションで並び替えとか ユーザがドラッグアンドドロップで並び替えとかしていた場合 その非同期処理中に hyperhtml や lit-html の更新が入ってしまうケースも考えられますしなかなか難しいところです