// History entries // reorder: { type:'reorder', files:[...], affectedPaths: Set|null } // delete: { type:'delete', files:[...] /* filesAfter */, // deletedEntries:[{name, originalPath, stagingPath}] /* mutable */ } const MAX = 50; let _stack = []; let _cursor = -1; let _onUpdate = null; export function setOnUpdate(fn) { _onUpdate = fn; } // ── Push ───────────────────────────────────────── export function pushReorder(files, affectedPaths = null) { _truncate(); _stack.push({ type: 'reorder', files: [...files], affectedPaths: affectedPaths ? new Set(affectedPaths) : null, }); _finalize(); } export function pushDelete(filesAfter, deletedEntries) { _truncate(); _stack.push({ type: 'delete', files: [...filesAfter], deletedEntries }); _finalize(); } function _truncate() { _stack = _stack.slice(0, _cursor + 1); } function _finalize() { if (_stack.length > MAX) _stack.shift(); _cursor = _stack.length - 1; _onUpdate?.(); } // ── Undo / Redo ─────────────────────────────────── // Returns { entry, targetFiles } // entry — the entry being undone/redone (for type-specific side-effects) // targetFiles — the files[] to restore in state export function undo() { if (!canUndo()) return null; const entry = _stack[_cursor]; // operation being undone --_cursor; _onUpdate?.(); return { entry, targetFiles: [..._stack[_cursor].files] }; } export function redo() { if (!canRedo()) return null; ++_cursor; _onUpdate?.(); const entry = _stack[_cursor]; return { entry, targetFiles: [...entry.files] }; } // Peek at the next redo entry without moving cursor (for pre-processing) export function peekRedo() { return canRedo() ? _stack[_cursor + 1] : null; } export function canUndo() { return _cursor > 0; } export function canRedo() { return _cursor < _stack.length - 1; } export function reset() { _stack = []; _cursor = -1; _onUpdate?.(); }