/** * The behaviors entered by double-clicking call nodes, root first. Either empty or at least two * long, and always ending at the behavior the graph shows – anything else opened it otherwise. */ export type Trail = readonly string[]; /** Opening `to` from a call node in `from`. A behavior already on the trail is a step back to it. */ export function descend(trail: Trail, from: string, to: string): Trail { const base = trail.at(-1) === from ? trail : [from]; const known = base.indexOf(to); return known >= 0 ? backTo(base, known) : [...base, to]; } /** Clicking crumb `index`; back at the root there is nothing left to show. */ export function backTo(trail: Trail, index: number): Trail { const next = trail.slice(0, index + 1); return next.length > 1 ? next : []; } /** What survives the graph showing `shown`: only a trail that still leads there, every step still alive. */ export function follow(trail: Trail, shown: string, exists: (id: string) => boolean): Trail { return trail.at(-1) === shown && trail.every(exists) ? trail : []; }