This commit is contained in:
Hunter
2026-08-07 17:11:04 -04:00
parent 6702030406
commit 45b1539c68
12 changed files with 851 additions and 61 deletions

View File

@@ -0,0 +1,64 @@
import { ref, computed, shallowRef } from 'vue'
import { emptyView, sameView, type ViewState } from '../lib/view-state'
/**
* The filter history: a queue of frames plus a cursor into it.
*
* This is deliberately the app's own history rather than the browser's.
* Browser history cannot be truncated — pushing after going back leaves the
* forward entries reachable by gesture — and it is shared with whatever the
* user did before arriving here, so "back" would eventually walk off the
* site mid-investigation. Owning the queue means the URL can be rewritten in
* place (replaceState) and still be exactly as linkable.
*/
export function useFrames() {
const frames = shallowRef<ViewState[]>([emptyView()])
const index = ref(0)
const current = computed(() => frames.value[index.value] ?? emptyView())
const canBack = computed(() => index.value > 0)
const canForward = computed(() => index.value < frames.value.length - 1)
/**
* Record a frame. A change made from anywhere but the newest frame discards
* everything after the cursor first: the user has branched, and the frames
* they walked back past are no longer reachable from where they now are.
*
* The equality check is what makes replay safe. Stepping back sets the
* filter refs to the frame at the cursor, which re-triggers the watchers
* that call this — and a frame identical to the current one is dropped, so
* no flag is needed to tell a replay from a real change.
*/
function push(v: ViewState) {
if (sameView(v, current.value)) return
const kept = frames.value.slice(0, index.value + 1)
kept.push(v)
frames.value = kept
index.value = kept.length - 1
}
/** Drop the whole queue — used when a different dump is opened. */
function reset(v: ViewState = emptyView()) {
frames.value = [v]
index.value = 0
}
const go = (delta: number) => {
const next = index.value + delta
if (next < 0 || next >= frames.value.length) return null
index.value = next
return frames.value[next]!
}
return {
frames,
index,
current,
canBack,
canForward,
push,
reset,
back: () => go(-1),
forward: () => go(1),
}
}