@weasel-js/history 1.9.3 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +71 -33
- package/dist/index.js +286 -173
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,22 +5,23 @@
|
|
|
5
5
|
* Adapters are intentionally typed loosely here so different op types can
|
|
6
6
|
* require different adapter capabilities. Each op is responsible for
|
|
7
7
|
* narrowing the adapter via the methods it calls.
|
|
8
|
-
*
|
|
9
|
-
* Lives here rather than in `@weasel-js/core` because an invertible,
|
|
10
|
-
* replayable mutation is a history concept: this package is what pushes ops
|
|
11
|
-
* onto a stack, inverts them, coalesces them, and rebuilds them from a
|
|
12
|
-
* serialized snapshot. Nothing about the shape is core-specific — it names no
|
|
13
|
-
* scene, node, or pose type. Core re-exports it from `core/ops/types` so its
|
|
14
|
-
* own call sites read unchanged.
|
|
15
8
|
*/
|
|
16
9
|
interface Op {
|
|
17
10
|
/** Apply the mutation. Return `false` (or `'noop'`) to signal that
|
|
18
11
|
* nothing changed — the history layer then skips pushing an undo
|
|
19
12
|
* entry for the batch when *every* op reports no-op. Returning
|
|
20
|
-
* `undefined`/`void` means "mutated"
|
|
21
|
-
* don't need to change). */
|
|
13
|
+
* `undefined`/`void` means "mutated". */
|
|
22
14
|
apply(adapter: unknown): void | boolean | 'noop';
|
|
23
15
|
invert(): Op;
|
|
16
|
+
/** Leave this op out of the recorded entry when its first apply reports a
|
|
17
|
+
* no-op. For an op an owner appends to every edit in case it is needed:
|
|
18
|
+
* left in, it would stop the edit coalescing with one that needed none. */
|
|
19
|
+
elideNoop?: boolean;
|
|
20
|
+
/** What the entry records in this op's place once it has applied: the ops
|
|
21
|
+
* that replay what it did. For an op whose effect is decided as it runs,
|
|
22
|
+
* so that undo and redo return to what it wrote rather than working it out
|
|
23
|
+
* again. Read once, after the batch's first apply; never on a replay. */
|
|
24
|
+
caused?(): Op[];
|
|
24
25
|
label?: string;
|
|
25
26
|
coalesceKey?: string;
|
|
26
27
|
/** Stable factory name for op-registry lookup. Kit-emitted ops always
|
|
@@ -49,7 +50,7 @@ interface BeginJournalOptions {
|
|
|
49
50
|
* A scoped sub-history forked from a `History`, opened by
|
|
50
51
|
* `history.beginJournal()`. Applies, undoes and redoes against the same
|
|
51
52
|
* adapter as its parent, but keeps its entries to itself: `commit` flushes the
|
|
52
|
-
* journal's
|
|
53
|
+
* journal's applied ops to the parent as one entry, `cancel` rewinds them
|
|
53
54
|
* and contributes nothing. Use it when a self-contained editing session (a
|
|
54
55
|
* text edit, a modal drag) should collapse to a single step in the parent's
|
|
55
56
|
* undo stack while still offering undo *within* the session.
|
|
@@ -85,6 +86,10 @@ interface Journal {
|
|
|
85
86
|
isActive(): boolean;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* A history's entries, their wire form, and the trip between the two.
|
|
91
|
+
*/
|
|
92
|
+
|
|
88
93
|
/** Wire form of a single op inside a serialized history. The pair
|
|
89
94
|
* `(name, args)` reconstructs a live `Op` via the op-factory registry. */
|
|
90
95
|
interface SerializedOp {
|
|
@@ -107,8 +112,10 @@ interface SerializedHistoryEntry {
|
|
|
107
112
|
selectionAfter?: readonly string[];
|
|
108
113
|
}
|
|
109
114
|
/** Snapshot of an entire `History` instance. Designed to live alongside the
|
|
110
|
-
* scene snapshot
|
|
111
|
-
*
|
|
115
|
+
* scene snapshot so a reload restores the undo / redo stacks to exactly
|
|
116
|
+
* where they were. Op `args` are carried by reference, so the snapshot is
|
|
117
|
+
* as serializable as they are: JSON drops a function an op's args hold,
|
|
118
|
+
* where a structured clone (IndexedDB, `postMessage`) throws on it. */
|
|
112
119
|
interface SerializedHistory {
|
|
113
120
|
version: 1;
|
|
114
121
|
undoStack: SerializedHistoryEntry[];
|
|
@@ -117,17 +124,30 @@ interface SerializedHistory {
|
|
|
117
124
|
redoStack: SerializedHistoryEntry[];
|
|
118
125
|
nextEntryId: number;
|
|
119
126
|
/** Futures kept by a branching history, each under the id of the entry it
|
|
120
|
-
* forks from (
|
|
127
|
+
* forks from (`floor`'s for the oldest reachable state), entries in time
|
|
128
|
+
* order. Absent when none. */
|
|
121
129
|
branches?: {
|
|
122
130
|
at: number;
|
|
123
131
|
entries: SerializedHistoryEntry[];
|
|
124
132
|
}[];
|
|
125
|
-
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
|
|
133
|
+
/** The id naming the state beneath the undo stack: that of the last undo
|
|
134
|
+
* entry evicted or left out of the snapshot. Absent, and read as 0, when
|
|
135
|
+
* the stack reaches back to the start. */
|
|
136
|
+
floor?: number;
|
|
137
|
+
/** Entries left out: those with an op lacking a `name`, which can't
|
|
138
|
+
* round-trip through the op-factory registry, and those only reachable
|
|
139
|
+
* across one. Always present (zero when nothing was dropped) so callers
|
|
140
|
+
* can detect loss without parsing the debug log. */
|
|
129
141
|
droppedEntries: number;
|
|
130
142
|
}
|
|
143
|
+
/** Diagnostics sink for {@link CreateHistoryOptions.debug}. Messages arrive
|
|
144
|
+
* pre-formatted and unconditional; deciding whether to emit them is the
|
|
145
|
+
* caller's job. */
|
|
146
|
+
interface HistoryLogger {
|
|
147
|
+
log(message: string): void;
|
|
148
|
+
warn(message: string): void;
|
|
149
|
+
}
|
|
150
|
+
|
|
131
151
|
/** Snapshot of an entry handed to `onEvict` when it permanently leaves the
|
|
132
152
|
* reachable stacks. Ops are live references — read `name`/`args`, don't
|
|
133
153
|
* mutate. */
|
|
@@ -197,7 +217,9 @@ interface History {
|
|
|
197
217
|
/** Walk the history forward/back until exactly `n` entries are on the
|
|
198
218
|
* undo stack (0 ≤ n ≤ entries().undo.length + entries().redo.length).
|
|
199
219
|
* Equivalent to repeated `undo()`/`redo()` calls but doesn't bother
|
|
200
|
-
* rebuilding entry snapshots between steps. No-op if already at `n`.
|
|
220
|
+
* rebuilding entry snapshots between steps. No-op if already at `n`.
|
|
221
|
+
* When a step throws, as with `undo` and `redo`, the ops it ran are
|
|
222
|
+
* inverted and the history stays where the call found it. */
|
|
201
223
|
goto(n: number): void;
|
|
202
224
|
/** The `timestamp` of entry `i`, counting in time order through the undo
|
|
203
225
|
* stack and on into the redo stack, so `timestampAt(undoDepth())` is the
|
|
@@ -231,11 +253,13 @@ interface History {
|
|
|
231
253
|
/** Subscribe to history changes. Fires after every push/undo/redo/
|
|
232
254
|
* clear/coalesce. Returns an unsubscribe fn. */
|
|
233
255
|
subscribe(listener: () => void): () => void;
|
|
234
|
-
/** Snapshot the undo + redo stacks
|
|
235
|
-
*
|
|
236
|
-
* `name`
|
|
237
|
-
*
|
|
238
|
-
* entry
|
|
256
|
+
/** Snapshot the undo + redo stacks as `(name, args)` pairs — see
|
|
257
|
+
* `SerializedHistory` for what that takes to store. An entry with an op
|
|
258
|
+
* missing a `name` can't round-trip, and neither can anything that is only
|
|
259
|
+
* reached by stepping across it: the snapshot keeps the undo entries newer
|
|
260
|
+
* than the last such entry, the redo entries nearer than the first, and
|
|
261
|
+
* the branches forking from an entry it kept. The rest is counted in
|
|
262
|
+
* `droppedEntries`. The in-memory stacks aren't modified. */
|
|
239
263
|
serialize(): SerializedHistory;
|
|
240
264
|
/** Replace the current undo + redo stacks with the deserialized contents
|
|
241
265
|
* of `snapshot`. Ops are rebuilt via the `rebuildOp` option when
|
|
@@ -318,8 +342,9 @@ interface CreateHistoryOptions {
|
|
|
318
342
|
/** Keep the redo future when a push is made while there is one, as a branch
|
|
319
343
|
* `branches()` lists and `switchBranch` returns to, instead of dropping it.
|
|
320
344
|
* Undo and redo still walk one line, the current branch. An entry evicted by
|
|
321
|
-
* `historyLimit` or `prune` takes the branches forking from
|
|
322
|
-
*
|
|
345
|
+
* `historyLimit` or `prune` takes along the branches forking from the state
|
|
346
|
+
* before it, which nothing can return to; the ones forking from the state
|
|
347
|
+
* it leaves stay, at the bottom of the undo stack. Default `false`. */
|
|
323
348
|
branching?: boolean;
|
|
324
349
|
/** Custom op rebuilder consulted by `restore()` before the global
|
|
325
350
|
* op-factory registry. Return `null` to fall through (global registry,
|
|
@@ -339,14 +364,27 @@ interface CreateHistoryOptions {
|
|
|
339
364
|
* is what makes `DEBUG=history` work for kit consumers. */
|
|
340
365
|
debug?: HistoryLogger;
|
|
341
366
|
}
|
|
342
|
-
/** Diagnostics sink for {@link CreateHistoryOptions.debug}. Messages arrive
|
|
343
|
-
* pre-formatted and unconditional; deciding whether to emit them is the
|
|
344
|
-
* caller's job. */
|
|
345
|
-
interface HistoryLogger {
|
|
346
|
-
log(message: string): void;
|
|
347
|
-
warn(message: string): void;
|
|
348
|
-
}
|
|
349
367
|
/** Build an op-batched undo/redo `History`. The adapter is passed to each op's `apply`/`invert`. */
|
|
350
368
|
declare function createHistory(adapter: unknown, options?: CreateHistoryOptions): History;
|
|
351
369
|
|
|
352
|
-
|
|
370
|
+
/** What a {@link swapOp} carries as its `args`: the value it leaves and the one it makes. */
|
|
371
|
+
interface SwapArgs<V> {
|
|
372
|
+
before: V;
|
|
373
|
+
after: V;
|
|
374
|
+
}
|
|
375
|
+
/** What {@link swapOp} is built from. */
|
|
376
|
+
interface SwapOpOptions<V> extends SwapArgs<V> {
|
|
377
|
+
/** The op's `name`, which a history's `rebuildOp` matches to rebuild it from a snapshot. */
|
|
378
|
+
name: string;
|
|
379
|
+
label: string;
|
|
380
|
+
/** Puts a value in force. Called with `after` to apply and `before` to undo. */
|
|
381
|
+
emit(value: V): void;
|
|
382
|
+
coalesceKey?: string;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* An edit as the swap of one whole value for another. For a value held immutably the two snapshots are
|
|
386
|
+
* references, and the op carries both as `args`, so a serialized history can rebuild the step.
|
|
387
|
+
*/
|
|
388
|
+
declare function swapOp<V>({ name, label, before, after, emit, coalesceKey }: SwapOpOptions<V>): Op;
|
|
389
|
+
|
|
390
|
+
export { type BeginJournalOptions, type CreateHistoryOptions, type EvictedEntry, type History, type HistoryBranch, type HistoryEntry, type HistoryLogger, type HistorySelection, type Journal, type Op, type RecordEntryOptions, type SerializedHistory, type SerializedHistoryEntry, type SerializedOp, type SwapArgs, type SwapOpOptions, createHistory, swapOp };
|
package/dist/index.js
CHANGED
|
@@ -58,10 +58,13 @@ function createJournalInternal(parent, adapter, opts, onClose, inherited = {}) {
|
|
|
58
58
|
},
|
|
59
59
|
commit(label) {
|
|
60
60
|
if (state !== "active") throw new Error("Journal is not active");
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
const { forwardOps, baseOps } = _journalAccessInternal(inner).appliedOps();
|
|
62
|
+
_journalAccessInternal(parent).record(
|
|
63
|
+
forwardOps,
|
|
64
|
+
baseOps,
|
|
65
|
+
label,
|
|
66
|
+
selectionBefore ? { selectionBefore } : void 0
|
|
67
|
+
);
|
|
65
68
|
state = "closed";
|
|
66
69
|
RESUMERS.delete(journal);
|
|
67
70
|
onClose?.();
|
|
@@ -93,7 +96,163 @@ function createJournalInternal(parent, adapter, opts, onClose, inherited = {}) {
|
|
|
93
96
|
return journal;
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
// src/serialized.ts
|
|
100
|
+
function snapshot(stacks, logger) {
|
|
101
|
+
const { undoStack, redoStack, forks, floorId, nextEntryId } = stacks;
|
|
102
|
+
let total = 0;
|
|
103
|
+
const project = (entries) => {
|
|
104
|
+
total += entries.length;
|
|
105
|
+
return entries.map((e) => entryToSerial(e, logger));
|
|
106
|
+
};
|
|
107
|
+
const nearest = (stack) => {
|
|
108
|
+
const serial = project(stack);
|
|
109
|
+
return serial.slice(serial.lastIndexOf(null) + 1);
|
|
110
|
+
};
|
|
111
|
+
const undo = nearest(undoStack);
|
|
112
|
+
const redo = nearest(redoStack);
|
|
113
|
+
const floor = undo.length === undoStack.length ? floorId : undoStack[undoStack.length - undo.length - 1].id;
|
|
114
|
+
const reachable = /* @__PURE__ */ new Set([floor, ...[...undo, ...redo].map((e) => e.id)]);
|
|
115
|
+
let pending = [...forks].flatMap(([at, kept]) => kept.map((b) => {
|
|
116
|
+
const serial = project(b);
|
|
117
|
+
const cut = serial.indexOf(null);
|
|
118
|
+
return { at, entries: cut < 0 ? serial : serial.slice(0, cut) };
|
|
119
|
+
})).filter((b) => b.entries.length > 0);
|
|
120
|
+
const branches = [];
|
|
121
|
+
for (let grew = true; grew; ) {
|
|
122
|
+
grew = false;
|
|
123
|
+
const waiting = [];
|
|
124
|
+
for (const b of pending) {
|
|
125
|
+
if (!reachable.has(b.at)) {
|
|
126
|
+
waiting.push(b);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
branches.push(b);
|
|
130
|
+
for (const e of b.entries) reachable.add(e.id);
|
|
131
|
+
grew = true;
|
|
132
|
+
}
|
|
133
|
+
pending = waiting;
|
|
134
|
+
}
|
|
135
|
+
const carried = undo.length + redo.length + branches.reduce((n, b) => n + b.entries.length, 0);
|
|
136
|
+
return {
|
|
137
|
+
version: 1,
|
|
138
|
+
undoStack: undo,
|
|
139
|
+
redoStack: redo,
|
|
140
|
+
...branches.length > 0 ? { branches } : {},
|
|
141
|
+
...floor !== 0 ? { floor } : {},
|
|
142
|
+
nextEntryId,
|
|
143
|
+
droppedEntries: total - carried
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function rebuilt(snapshot2, custom, logger) {
|
|
147
|
+
const entry = (se) => serialToEntry(se, custom, logger);
|
|
148
|
+
const undoStack = snapshot2.undoStack.map(entry);
|
|
149
|
+
const redoStack = snapshot2.redoStack.map(entry);
|
|
150
|
+
const forks = /* @__PURE__ */ new Map();
|
|
151
|
+
for (const b of snapshot2.branches ?? []) {
|
|
152
|
+
const entries = b.entries.map(entry);
|
|
153
|
+
const kept = forks.get(b.at);
|
|
154
|
+
if (kept) kept.push(entries);
|
|
155
|
+
else forks.set(b.at, [entries]);
|
|
156
|
+
}
|
|
157
|
+
let nextEntryId = snapshot2.nextEntryId;
|
|
158
|
+
const seen = [undoStack, redoStack, ...[...forks.values()].flat()];
|
|
159
|
+
for (const entries of seen) for (const e of entries) if (e.id >= nextEntryId) nextEntryId = e.id + 1;
|
|
160
|
+
return { undoStack, redoStack, forks, floorId: snapshot2.floor ?? 0, nextEntryId };
|
|
161
|
+
}
|
|
162
|
+
function touchedIdsFromOps(ops) {
|
|
163
|
+
const ids = /* @__PURE__ */ new Set();
|
|
164
|
+
for (const op of ops) {
|
|
165
|
+
if (op.args === null || typeof op.args !== "object") continue;
|
|
166
|
+
const a = op.args;
|
|
167
|
+
if (typeof a["id"] === "string") {
|
|
168
|
+
ids.add(a["id"]);
|
|
169
|
+
} else if (a["node"] !== null && typeof a["node"] === "object") {
|
|
170
|
+
const n = a["node"];
|
|
171
|
+
if (typeof n["id"] === "string") ids.add(n["id"]);
|
|
172
|
+
} else if (Array.isArray(a["nodes"])) {
|
|
173
|
+
for (const n of a["nodes"]) {
|
|
174
|
+
const id = n !== null && typeof n === "object" ? n["id"] : void 0;
|
|
175
|
+
if (typeof id === "string") ids.add(id);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return ids;
|
|
180
|
+
}
|
|
181
|
+
function opToSerial(op) {
|
|
182
|
+
if (typeof op.name !== "string") return null;
|
|
183
|
+
return { name: op.name, args: op.args };
|
|
184
|
+
}
|
|
185
|
+
function entryToSerial(e, logger) {
|
|
186
|
+
const forwardOps = [];
|
|
187
|
+
for (const op of e.forwardOps) {
|
|
188
|
+
const s = opToSerial(op);
|
|
189
|
+
if (s === null) {
|
|
190
|
+
logger.log(`serialize: dropping entry id=${e.id} "${e.label}" \u2014 forwardOp without name`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
forwardOps.push(s);
|
|
194
|
+
}
|
|
195
|
+
const baseOps = [];
|
|
196
|
+
for (const op of e.baseOps) {
|
|
197
|
+
const s = opToSerial(op);
|
|
198
|
+
if (s === null) {
|
|
199
|
+
logger.log(`serialize: dropping entry id=${e.id} "${e.label}" \u2014 baseOp without name`);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
baseOps.push(s);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
id: e.id,
|
|
206
|
+
label: e.label,
|
|
207
|
+
forwardOps,
|
|
208
|
+
baseOps,
|
|
209
|
+
pushes: e.pushes,
|
|
210
|
+
timestamp: e.timestamp,
|
|
211
|
+
...e.selectionBefore ? { selectionBefore: e.selectionBefore } : {},
|
|
212
|
+
...e.selectionAfter ? { selectionAfter: e.selectionAfter } : {}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function placeholderOp(name, args, label) {
|
|
216
|
+
const op = {
|
|
217
|
+
name,
|
|
218
|
+
args,
|
|
219
|
+
label,
|
|
220
|
+
apply: () => "noop",
|
|
221
|
+
invert: () => op
|
|
222
|
+
};
|
|
223
|
+
return op;
|
|
224
|
+
}
|
|
225
|
+
function rebuildSerialOp(so, label, custom, logger) {
|
|
226
|
+
const viaCustom = custom ? custom(so.name, so.args) : null;
|
|
227
|
+
if (viaCustom !== null) return viaCustom;
|
|
228
|
+
logger.log(`restore: unknown op name "${so.name}" \u2014 substituting no-op placeholder`);
|
|
229
|
+
return placeholderOp(so.name, so.args, label);
|
|
230
|
+
}
|
|
231
|
+
function serialToEntry(se, custom, logger) {
|
|
232
|
+
const forwardOps = se.forwardOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));
|
|
233
|
+
const baseOps = se.baseOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));
|
|
234
|
+
return {
|
|
235
|
+
id: se.id,
|
|
236
|
+
label: se.label,
|
|
237
|
+
forwardOps,
|
|
238
|
+
baseOps,
|
|
239
|
+
timestamp: se.timestamp ?? 0,
|
|
240
|
+
pushes: se.pushes ?? 1,
|
|
241
|
+
// Re-derive touchedIds from the rebuilt ops rather than trying to
|
|
242
|
+
// round-trip the Set through the serialized form (Sets aren't JSON-safe).
|
|
243
|
+
touchedIds: touchedIdsFromOps(forwardOps),
|
|
244
|
+
...se.selectionBefore ? { selectionBefore: [...se.selectionBefore] } : {},
|
|
245
|
+
...se.selectionAfter ? { selectionAfter: [...se.selectionAfter] } : {}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
96
249
|
// src/history.ts
|
|
250
|
+
var JOURNAL_ACCESS = /* @__PURE__ */ new WeakMap();
|
|
251
|
+
function _journalAccessInternal(history) {
|
|
252
|
+
const access = JOURNAL_ACCESS.get(history);
|
|
253
|
+
if (!access) throw new Error("Not a history built by createHistory");
|
|
254
|
+
return access;
|
|
255
|
+
}
|
|
97
256
|
var SILENT = { log: () => {
|
|
98
257
|
}, warn: () => {
|
|
99
258
|
} };
|
|
@@ -107,6 +266,7 @@ function createHistory(adapter, options = {}) {
|
|
|
107
266
|
const onEvict = options.onEvict;
|
|
108
267
|
const branching = options.branching ?? false;
|
|
109
268
|
const forks = /* @__PURE__ */ new Map();
|
|
269
|
+
let floorId = 0;
|
|
110
270
|
const customRebuild = options.rebuildOp;
|
|
111
271
|
const selection = options.selection;
|
|
112
272
|
const logger = options.debug ?? SILENT;
|
|
@@ -137,8 +297,17 @@ function createHistory(adapter, options = {}) {
|
|
|
137
297
|
for (const branch of kept) evict(branch);
|
|
138
298
|
}
|
|
139
299
|
}
|
|
300
|
+
function evictFloor(entries) {
|
|
301
|
+
for (const e of entries) {
|
|
302
|
+
reportEvicted([e]);
|
|
303
|
+
const stranded = forks.get(floorId);
|
|
304
|
+
forks.delete(floorId);
|
|
305
|
+
floorId = e.id;
|
|
306
|
+
for (const branch of stranded ?? []) evict(branch);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
140
309
|
function forkPoint() {
|
|
141
|
-
return undoStack[undoStack.length - 1]?.id ??
|
|
310
|
+
return undoStack[undoStack.length - 1]?.id ?? floorId;
|
|
142
311
|
}
|
|
143
312
|
function retireRedo(at) {
|
|
144
313
|
if (redoStack.length === 0) return;
|
|
@@ -150,17 +319,33 @@ function createHistory(adapter, options = {}) {
|
|
|
150
319
|
}
|
|
151
320
|
function enforceLimit() {
|
|
152
321
|
while (undoStack.length > historyLimit) {
|
|
153
|
-
|
|
322
|
+
evictFloor([undoStack.shift()]);
|
|
154
323
|
}
|
|
155
324
|
}
|
|
156
|
-
function
|
|
157
|
-
for (const op of ops)
|
|
325
|
+
function applyUnwinding(ops) {
|
|
326
|
+
for (const op of ops) {
|
|
327
|
+
try {
|
|
328
|
+
op.apply(adapter);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
logger.warn(`unwinding a failed step: op "${op.name ?? "?"}" threw: ${String(err)}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
158
333
|
}
|
|
159
|
-
function
|
|
334
|
+
function applyAllOrNothing(ops, settle, idle) {
|
|
160
335
|
let anyMutated = false;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
336
|
+
let done = 0;
|
|
337
|
+
try {
|
|
338
|
+
for (; done < ops.length; done++) {
|
|
339
|
+
const r = ops[done].apply(adapter);
|
|
340
|
+
if (r !== false && r !== "noop") anyMutated = true;
|
|
341
|
+
else idle?.add(ops[done]);
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
if (done > 0) {
|
|
345
|
+
applyUnwinding(ops.slice(0, done).reverse().map((op) => op.invert()));
|
|
346
|
+
if (settle) applyUnwinding(settle());
|
|
347
|
+
}
|
|
348
|
+
throw err;
|
|
164
349
|
}
|
|
165
350
|
return anyMutated;
|
|
166
351
|
}
|
|
@@ -172,16 +357,32 @@ function createHistory(adapter, options = {}) {
|
|
|
172
357
|
}
|
|
173
358
|
function stepBack(entry) {
|
|
174
359
|
const leaving = readSelection();
|
|
360
|
+
const coalesced = entry.forwardOps !== entry.baseOps;
|
|
361
|
+
applyAllOrNothing(invertEntry(entry), coalesced ? () => entry.forwardOps : void 0);
|
|
175
362
|
if (leaving) entry.selectionAfter = leaving;
|
|
176
|
-
applyOps(invertEntry(entry));
|
|
177
363
|
if (selection && entry.selectionBefore) selection.set(entry.selectionBefore);
|
|
178
364
|
}
|
|
179
365
|
function stepForward(entry) {
|
|
180
366
|
const leaving = readSelection();
|
|
367
|
+
const coalesced = entry.forwardOps !== entry.baseOps;
|
|
368
|
+
applyAllOrNothing(entry.forwardOps, coalesced ? () => invertEntry(entry) : void 0);
|
|
181
369
|
if (leaving) entry.selectionBefore = leaving;
|
|
182
|
-
applyOps(entry.forwardOps);
|
|
183
370
|
if (selection && entry.selectionAfter) selection.set(entry.selectionAfter);
|
|
184
371
|
}
|
|
372
|
+
function walkTo(n) {
|
|
373
|
+
while (undoStack.length > n) {
|
|
374
|
+
const entry = undoStack[undoStack.length - 1];
|
|
375
|
+
stepBack(entry);
|
|
376
|
+
undoStack.pop();
|
|
377
|
+
redoStack.push(entry);
|
|
378
|
+
}
|
|
379
|
+
while (undoStack.length < n && redoStack.length > 0) {
|
|
380
|
+
const entry = redoStack[redoStack.length - 1];
|
|
381
|
+
stepForward(entry);
|
|
382
|
+
redoStack.pop();
|
|
383
|
+
undoStack.push(entry);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
185
386
|
function canCoalesce(top, incoming) {
|
|
186
387
|
if (coalesceWindowMs <= 0) return false;
|
|
187
388
|
if (version !== coalesceAnchorVersion) return false;
|
|
@@ -203,16 +404,19 @@ function createHistory(adapter, options = {}) {
|
|
|
203
404
|
}
|
|
204
405
|
return true;
|
|
205
406
|
}
|
|
206
|
-
function pushOrCoalesce(
|
|
207
|
-
if (
|
|
407
|
+
function pushOrCoalesce(applied, label) {
|
|
408
|
+
if (applied.length === 0) return;
|
|
208
409
|
const selectionBefore = readSelection();
|
|
209
|
-
const
|
|
410
|
+
const idle = /* @__PURE__ */ new Set();
|
|
411
|
+
const anyMutated = applyAllOrNothing(applied, void 0, idle);
|
|
412
|
+
const ops = (idle.size === 0 ? applied : applied.filter((op) => !(op.elideNoop && idle.has(op)))).flatMap((op) => op.caused?.() ?? [op]);
|
|
210
413
|
if (!anyMutated) {
|
|
211
414
|
logger.warn(
|
|
212
415
|
`'${label}' batch was a no-op \u2014 every op reported false/'noop'. Skipping the undo entry; consider gating the dispatch upstream to avoid the wasted work.`
|
|
213
416
|
);
|
|
214
417
|
return;
|
|
215
418
|
}
|
|
419
|
+
if (ops.length === 0) return;
|
|
216
420
|
const incoming = touchedIdsFromOps(ops);
|
|
217
421
|
const top = undoStack[undoStack.length - 1];
|
|
218
422
|
if (top && canCoalesce(top, ops)) {
|
|
@@ -246,7 +450,24 @@ function createHistory(adapter, options = {}) {
|
|
|
246
450
|
bump();
|
|
247
451
|
coalesceAnchorVersion = version;
|
|
248
452
|
}
|
|
249
|
-
|
|
453
|
+
function record(forwardOps, baseOps, label, options2 = {}) {
|
|
454
|
+
if (forwardOps.length === 0) return;
|
|
455
|
+
const at = forkPoint();
|
|
456
|
+
undoStack.push({
|
|
457
|
+
id: nextEntryId++,
|
|
458
|
+
forwardOps,
|
|
459
|
+
baseOps,
|
|
460
|
+
label,
|
|
461
|
+
timestamp: now(),
|
|
462
|
+
pushes: 1,
|
|
463
|
+
touchedIds: touchedIdsFromOps(forwardOps),
|
|
464
|
+
...options2.selectionBefore ? { selectionBefore: [...options2.selectionBefore] } : {}
|
|
465
|
+
});
|
|
466
|
+
retireRedo(at);
|
|
467
|
+
enforceLimit();
|
|
468
|
+
bump();
|
|
469
|
+
}
|
|
470
|
+
const history = {
|
|
250
471
|
apply(op, label) {
|
|
251
472
|
pushOrCoalesce([op], label ?? op.label ?? "");
|
|
252
473
|
},
|
|
@@ -254,17 +475,13 @@ function createHistory(adapter, options = {}) {
|
|
|
254
475
|
pushOrCoalesce(ops, label);
|
|
255
476
|
},
|
|
256
477
|
undo() {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
stepBack(entry);
|
|
260
|
-
redoStack.push(entry);
|
|
478
|
+
if (undoStack.length === 0) return;
|
|
479
|
+
walkTo(undoStack.length - 1);
|
|
261
480
|
bump();
|
|
262
481
|
},
|
|
263
482
|
redo() {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
stepForward(entry);
|
|
267
|
-
undoStack.push(entry);
|
|
483
|
+
if (redoStack.length === 0) return;
|
|
484
|
+
walkTo(undoStack.length + 1);
|
|
268
485
|
bump();
|
|
269
486
|
},
|
|
270
487
|
seal() {
|
|
@@ -279,6 +496,7 @@ function createHistory(adapter, options = {}) {
|
|
|
279
496
|
undoStack.length = 0;
|
|
280
497
|
redoStack.length = 0;
|
|
281
498
|
forks.clear();
|
|
499
|
+
floorId = 0;
|
|
282
500
|
if (had) bump();
|
|
283
501
|
},
|
|
284
502
|
entries() {
|
|
@@ -299,16 +517,17 @@ function createHistory(adapter, options = {}) {
|
|
|
299
517
|
goto(n) {
|
|
300
518
|
const total = undoStack.length + redoStack.length;
|
|
301
519
|
if (n < 0 || n > total || n === undoStack.length) return;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
undoStack.
|
|
520
|
+
const start = undoStack.length;
|
|
521
|
+
try {
|
|
522
|
+
walkTo(n);
|
|
523
|
+
} catch (err) {
|
|
524
|
+
try {
|
|
525
|
+
walkTo(start);
|
|
526
|
+
} catch (back) {
|
|
527
|
+
logger.warn(`goto: returning to depth ${start} threw: ${String(back)}`);
|
|
528
|
+
}
|
|
529
|
+
if (undoStack.length !== start) bump();
|
|
530
|
+
throw err;
|
|
312
531
|
}
|
|
313
532
|
bump();
|
|
314
533
|
},
|
|
@@ -328,7 +547,7 @@ function createHistory(adapter, options = {}) {
|
|
|
328
547
|
let k = 0;
|
|
329
548
|
while (k < undoStack.length && undoStack[k].timestamp < t) k++;
|
|
330
549
|
if (k === 0) return;
|
|
331
|
-
|
|
550
|
+
evictFloor(undoStack.splice(0, k));
|
|
332
551
|
bump();
|
|
333
552
|
},
|
|
334
553
|
branches() {
|
|
@@ -360,43 +579,9 @@ function createHistory(adapter, options = {}) {
|
|
|
360
579
|
listeners.delete(listener);
|
|
361
580
|
};
|
|
362
581
|
},
|
|
363
|
-
serialize() {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const s = entryToSerial(e, logger);
|
|
367
|
-
if (s === null) dropped++;
|
|
368
|
-
return s;
|
|
369
|
-
};
|
|
370
|
-
return {
|
|
371
|
-
version: 1,
|
|
372
|
-
undoStack: undoStack.map(project).filter((e) => e !== null),
|
|
373
|
-
redoStack: redoStack.map(project).filter((e) => e !== null),
|
|
374
|
-
...forks.size > 0 ? {
|
|
375
|
-
branches: [...forks].flatMap(([at, kept]) => kept.map((b) => ({
|
|
376
|
-
at,
|
|
377
|
-
entries: b.map(project).filter((e) => e !== null)
|
|
378
|
-
}))).filter((b) => b.entries.length > 0)
|
|
379
|
-
} : {},
|
|
380
|
-
nextEntryId,
|
|
381
|
-
droppedEntries: dropped
|
|
382
|
-
};
|
|
383
|
-
},
|
|
384
|
-
recordEntry(ops, label, options2 = {}) {
|
|
385
|
-
if (ops.length === 0) return;
|
|
386
|
-
const at = forkPoint();
|
|
387
|
-
undoStack.push({
|
|
388
|
-
id: nextEntryId++,
|
|
389
|
-
forwardOps: ops,
|
|
390
|
-
baseOps: ops,
|
|
391
|
-
label,
|
|
392
|
-
timestamp: now(),
|
|
393
|
-
pushes: 1,
|
|
394
|
-
touchedIds: touchedIdsFromOps(ops),
|
|
395
|
-
...options2.selectionBefore ? { selectionBefore: [...options2.selectionBefore] } : {}
|
|
396
|
-
});
|
|
397
|
-
retireRedo(at);
|
|
398
|
-
enforceLimit();
|
|
399
|
-
bump();
|
|
582
|
+
serialize: () => snapshot({ undoStack, redoStack, forks, floorId, nextEntryId }, logger),
|
|
583
|
+
recordEntry(ops, label, options2) {
|
|
584
|
+
record(ops, ops, label, options2);
|
|
400
585
|
},
|
|
401
586
|
allForwardOps() {
|
|
402
587
|
const out = [];
|
|
@@ -412,7 +597,7 @@ function createHistory(adapter, options = {}) {
|
|
|
412
597
|
if (activeJournal !== null && activeJournal.isActive()) {
|
|
413
598
|
throw new Error("A journal is already active \u2014 commit, cancel, or suspend it first");
|
|
414
599
|
}
|
|
415
|
-
const j = createJournalInternal(
|
|
600
|
+
const j = createJournalInternal(history, adapter, opts, () => {
|
|
416
601
|
activeJournal = null;
|
|
417
602
|
}, {
|
|
418
603
|
coalesceWindowMs,
|
|
@@ -430,112 +615,40 @@ function createHistory(adapter, options = {}) {
|
|
|
430
615
|
_resumeJournalInternal(journal);
|
|
431
616
|
activeJournal = journal;
|
|
432
617
|
},
|
|
433
|
-
restore(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
undoStack.push(serialToEntry(se, customRebuild, logger));
|
|
438
|
-
}
|
|
439
|
-
for (const se of snapshot.redoStack) {
|
|
440
|
-
redoStack.push(serialToEntry(se, customRebuild, logger));
|
|
441
|
-
}
|
|
618
|
+
restore(serialized) {
|
|
619
|
+
const next = rebuilt(serialized, customRebuild, logger);
|
|
620
|
+
undoStack.splice(0, undoStack.length, ...next.undoStack);
|
|
621
|
+
redoStack.splice(0, redoStack.length, ...next.redoStack);
|
|
442
622
|
forks.clear();
|
|
443
|
-
for (const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (kept) kept.push(entries);
|
|
447
|
-
else forks.set(b.at, [entries]);
|
|
448
|
-
}
|
|
449
|
-
nextEntryId = snapshot.nextEntryId;
|
|
450
|
-
for (const e of undoStack) if (e.id >= nextEntryId) nextEntryId = e.id + 1;
|
|
451
|
-
for (const e of redoStack) if (e.id >= nextEntryId) nextEntryId = e.id + 1;
|
|
452
|
-
for (const kept of forks.values()) for (const b of kept) for (const e of b) if (e.id >= nextEntryId) nextEntryId = e.id + 1;
|
|
623
|
+
for (const [at, kept] of next.forks) forks.set(at, kept);
|
|
624
|
+
floorId = next.floorId;
|
|
625
|
+
nextEntryId = next.nextEntryId;
|
|
453
626
|
bump();
|
|
454
627
|
}
|
|
455
628
|
};
|
|
629
|
+
JOURNAL_ACCESS.set(history, {
|
|
630
|
+
appliedOps: () => ({
|
|
631
|
+
forwardOps: undoStack.flatMap((e) => e.forwardOps),
|
|
632
|
+
baseOps: undoStack.flatMap((e) => e.baseOps)
|
|
633
|
+
}),
|
|
634
|
+
record
|
|
635
|
+
});
|
|
636
|
+
return history;
|
|
456
637
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const a = op.args;
|
|
462
|
-
if (typeof a["id"] === "string") {
|
|
463
|
-
ids.add(a["id"]);
|
|
464
|
-
} else if (a["node"] !== null && typeof a["node"] === "object") {
|
|
465
|
-
const n = a["node"];
|
|
466
|
-
if (typeof n["id"] === "string") ids.add(n["id"]);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return ids;
|
|
470
|
-
}
|
|
471
|
-
function opToSerial(op) {
|
|
472
|
-
if (typeof op.name !== "string") return null;
|
|
473
|
-
return { name: op.name, args: op.args };
|
|
474
|
-
}
|
|
475
|
-
function entryToSerial(e, logger) {
|
|
476
|
-
const forwardOps = [];
|
|
477
|
-
for (const op of e.forwardOps) {
|
|
478
|
-
const s = opToSerial(op);
|
|
479
|
-
if (s === null) {
|
|
480
|
-
logger.log(`serialize: dropping entry id=${e.id} "${e.label}" \u2014 forwardOp without name`);
|
|
481
|
-
return null;
|
|
482
|
-
}
|
|
483
|
-
forwardOps.push(s);
|
|
484
|
-
}
|
|
485
|
-
const baseOps = [];
|
|
486
|
-
for (const op of e.baseOps) {
|
|
487
|
-
const s = opToSerial(op);
|
|
488
|
-
if (s === null) {
|
|
489
|
-
logger.log(`serialize: dropping entry id=${e.id} "${e.label}" \u2014 baseOp without name`);
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
baseOps.push(s);
|
|
493
|
-
}
|
|
494
|
-
return {
|
|
495
|
-
id: e.id,
|
|
496
|
-
label: e.label,
|
|
497
|
-
forwardOps,
|
|
498
|
-
baseOps,
|
|
499
|
-
pushes: e.pushes,
|
|
500
|
-
timestamp: e.timestamp,
|
|
501
|
-
...e.selectionBefore ? { selectionBefore: e.selectionBefore } : {},
|
|
502
|
-
...e.selectionAfter ? { selectionAfter: e.selectionAfter } : {}
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
function placeholderOp(name, args, label) {
|
|
506
|
-
const op = {
|
|
507
|
-
name,
|
|
508
|
-
args,
|
|
638
|
+
|
|
639
|
+
// src/swapOp.ts
|
|
640
|
+
function swapOp({ name, label, before, after, emit, coalesceKey }) {
|
|
641
|
+
const forward = {
|
|
509
642
|
label,
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
function rebuildSerialOp(so, label, custom, logger) {
|
|
516
|
-
const viaCustom = custom ? custom(so.name, so.args) : null;
|
|
517
|
-
if (viaCustom !== null) return viaCustom;
|
|
518
|
-
logger.log(`restore: unknown op name "${so.name}" \u2014 substituting no-op placeholder`);
|
|
519
|
-
return placeholderOp(so.name, so.args, label);
|
|
520
|
-
}
|
|
521
|
-
function serialToEntry(se, custom, logger) {
|
|
522
|
-
const forwardOps = se.forwardOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));
|
|
523
|
-
const baseOps = se.baseOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));
|
|
524
|
-
return {
|
|
525
|
-
id: se.id,
|
|
526
|
-
label: se.label,
|
|
527
|
-
forwardOps,
|
|
528
|
-
baseOps,
|
|
529
|
-
timestamp: se.timestamp ?? 0,
|
|
530
|
-
pushes: se.pushes ?? 1,
|
|
531
|
-
// Re-derive touchedIds from the rebuilt ops rather than trying to
|
|
532
|
-
// round-trip the Set through the serialized form (Sets aren't JSON-safe).
|
|
533
|
-
touchedIds: touchedIdsFromOps(forwardOps),
|
|
534
|
-
...se.selectionBefore ? { selectionBefore: [...se.selectionBefore] } : {},
|
|
535
|
-
...se.selectionAfter ? { selectionAfter: [...se.selectionAfter] } : {}
|
|
643
|
+
name,
|
|
644
|
+
args: { before, after },
|
|
645
|
+
...coalesceKey !== void 0 ? { coalesceKey } : {},
|
|
646
|
+
apply: () => emit(after),
|
|
647
|
+
invert: () => ({ label, apply: () => emit(before), invert: () => forward })
|
|
536
648
|
};
|
|
649
|
+
return forward;
|
|
537
650
|
}
|
|
538
651
|
|
|
539
|
-
export { createHistory };
|
|
652
|
+
export { createHistory, swapOp };
|
|
540
653
|
//# sourceMappingURL=index.js.map
|
|
541
654
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/journal.ts","../src/history.ts"],"names":["options"],"mappings":"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","file":"index.js","sourcesContent":["import type { Op } from './op';\nimport { createHistory, type CreateHistoryOptions, type History, type HistoryEntry } from './history';\n\nconst RESUMERS = new WeakMap<Journal, () => void>();\n\n/** Called by `history.resumeJournal`. Not part of the public API. */\nexport function _resumeJournalInternal(j: Journal): void {\n const r = RESUMERS.get(j);\n if (!r) throw new Error('Journal is not resumable (already committed or cancelled)');\n r();\n}\n\n/** Options for `history.beginJournal()`. */\nexport interface BeginJournalOptions {\n /** Label for the single parent-history entry the journal flushes on commit. */\n label: string;\n /** Caller-supplied tag naming what this journal is scoped to — typically the\n * id of the node being edited. The history layer only carries it; callers\n * read it back off the journal to decide whether a suspended journal\n * matches what they are about to edit. */\n targetId?: string;\n /** Coalesce window for the journal's own entries, as\n * `CreateHistoryOptions.coalesceWindowMs`. Defaults to the parent's. */\n coalesceWindowMs?: number;\n}\n\n/**\n * A scoped sub-history forked from a `History`, opened by\n * `history.beginJournal()`. Applies, undoes and redoes against the same\n * adapter as its parent, but keeps its entries to itself: `commit` flushes the\n * journal's net forward ops to the parent as one entry, `cancel` rewinds them\n * and contributes nothing. Use it when a self-contained editing session (a\n * text edit, a modal drag) should collapse to a single step in the parent's\n * undo stack while still offering undo *within* the session.\n *\n * A journal is active, suspended or closed. `commit` and `cancel` are\n * terminal; `suspend` lets the parent be used again and can be reversed with\n * `history.resumeJournal()`. Every mutating method throws when the journal is\n * not active.\n */\nexport interface Journal {\n readonly targetId: string | undefined;\n readonly forkedAtEntryId: number;\n\n // Same operational surface as History\n applyBatch(ops: Op[], label: string): void;\n undo(): void;\n redo(): void;\n canUndo(): boolean;\n canRedo(): boolean;\n entries(): { undo: readonly HistoryEntry[]; redo: readonly HistoryEntry[] };\n /** As `History.seal`, for the journal's own entries. */\n seal(): void;\n /** Counter bumped on every change a reader of this journal could see: an\n * apply, undo, redo or coalesce inside it, and every lifecycle transition\n * (`isActive` changes). Pairs with `subscribe` for `useSyncExternalStore`. */\n getVersion(): number;\n /** Fires whenever `getVersion` bumps. Returns an unsubscribe fn. */\n subscribe(listener: () => void): () => void;\n\n // Lifecycle\n commit(label: string): void;\n cancel(): void;\n suspend(): void;\n isActive(): boolean;\n}\n\n/** Internal factory used by `createHistory`'s `beginJournal` method.\n * Not exported via the package's `index.ts` — callers go through\n * `history.beginJournal()`. */\nexport function createJournalInternal(\n parent: History,\n adapter: unknown,\n opts: BeginJournalOptions,\n onClose?: () => void,\n inherited: Pick<CreateHistoryOptions, 'coalesceWindowMs' | 'now' | 'debug' | 'selection'> = {},\n): Journal {\n const inner = createHistory(adapter, {\n ...inherited,\n ...(opts.coalesceWindowMs !== undefined ? { coalesceWindowMs: opts.coalesceWindowMs } : {}),\n });\n const selection = inherited.selection;\n let version = 0;\n const listeners = new Set<() => void>();\n function bump(): void {\n version++;\n for (const l of listeners) l();\n }\n inner.subscribe(bump);\n const forkedAtEntryId = parent.currentEntryId();\n // The whole session collapses to one parent entry, so the selection that\n // entry restores is the one the session opened under — not whatever the\n // last keystroke left.\n const selectionBefore = selection ? [...selection.get()] : undefined;\n type State = 'active' | 'suspended' | 'closed';\n let state: State = 'active';\n const targetId = opts.targetId;\n\n const journal: Journal = {\n targetId,\n forkedAtEntryId,\n\n applyBatch(ops: Op[], label: string): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.applyOps(ops, label);\n },\n undo(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.undo();\n },\n redo(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.redo();\n },\n canUndo(): boolean {\n return inner.canUndo();\n },\n canRedo(): boolean {\n return inner.canRedo();\n },\n entries() {\n return inner.entries();\n },\n seal(): void {\n inner.seal();\n },\n getVersion: () => version,\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n commit(label: string): void {\n if (state !== 'active') throw new Error('Journal is not active');\n const netOps = inner.allForwardOps();\n if (netOps.length > 0) {\n parent.recordEntry(netOps, label, selectionBefore ? { selectionBefore } : undefined);\n }\n state = 'closed';\n RESUMERS.delete(journal);\n onClose?.();\n bump();\n },\n cancel(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.goto(0);\n state = 'closed';\n RESUMERS.delete(journal);\n onClose?.();\n bump();\n },\n suspend(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n state = 'suspended';\n onClose?.();\n bump();\n },\n isActive(): boolean {\n return state === 'active';\n },\n };\n\n RESUMERS.set(journal, () => {\n if (state !== 'suspended') throw new Error('Journal is not suspended');\n state = 'active';\n bump();\n });\n\n return journal;\n}\n","import type { Op } from './op';\nimport { createJournalInternal, _resumeJournalInternal, type Journal, type BeginJournalOptions } from './journal';\n\ninterface Entry {\n /** Monotonic id assigned at first push. Stable across coalesce merges\n * (a merged entry keeps the original id) so UI lists keyed on `id` don't\n * flicker when the underlying entry mutates. */\n id: number;\n /** Forward ops — applied on redo, reflect the latest to-state after any\n * coalescing. Diverges from `baseOps` only after a coalesce. */\n forwardOps: Op[];\n /** Original ops at first push — their `.invert()` is what undo replays.\n * Preserved across coalesces so undo always returns to the original\n * pre-edit state, no matter how many coalesces happened. */\n baseOps: Op[];\n label: string;\n /** `now()` at last push or coalesce: gates the coalesce window, and is the\n * entry's place in time for `depthAt` and `prune`. */\n timestamp: number;\n /** See `HistoryEntry.pushes`. */\n pushes: number;\n /** Node ids touched by ops in this entry. See `HistoryEntry.touchedIds`. */\n touchedIds: ReadonlySet<string>;\n /** Selection as of just before this entry's ops ran; restored by undo.\n * Undefined when nothing supplied one (no `selection` option, or a\n * `recordEntry` call that captured none). */\n selectionBefore?: readonly string[];\n /** Selection as of just before this entry was last undone; restored by\n * redo. Written on the way past, so it reflects where the user actually\n * was rather than where the entry's ops left them. */\n selectionAfter?: readonly string[];\n}\n\n/** Wire form of a single op inside a serialized history. The pair\n * `(name, args)` reconstructs a live `Op` via the op-factory registry. */\nexport interface SerializedOp {\n name: string;\n args: unknown;\n}\n\n/** Wire form of one history entry. `forwardOps` / `baseOps` mirror the\n * in-memory entry's fields (see `Entry` above) but only carry the\n * serializable `(name, args)` projection of each op. */\nexport interface SerializedHistoryEntry {\n id: number;\n label: string;\n forwardOps: SerializedOp[];\n baseOps: SerializedOp[];\n /** See `HistoryEntry.pushes`. Absent in snapshots that predate it; read as 1. */\n pushes?: number;\n /** See `HistoryEntry.timestamp`. Absent in snapshots that predate it; read as 0. */\n timestamp?: number;\n selectionBefore?: readonly string[];\n selectionAfter?: readonly string[];\n}\n\n/** Snapshot of an entire `History` instance. Designed to live alongside the\n * scene snapshot in IDB so a reload restores the undo / redo stacks to\n * exactly where they were. */\nexport interface SerializedHistory {\n version: 1;\n undoStack: SerializedHistoryEntry[];\n /** Stored newest-first, mirroring the in-memory stack so a deserialized\n * history matches the original's `entries().redo` ordering. */\n redoStack: SerializedHistoryEntry[];\n nextEntryId: number;\n /** Futures kept by a branching history, each under the id of the entry it\n * forks from (0 for the start), entries in time order. Absent when none. */\n branches?: { at: number; entries: SerializedHistoryEntry[] }[];\n /** Entries dropped because at least one of their ops lacked a `name`\n * and therefore couldn't round-trip through the op-factory registry.\n * Always present (zero when nothing was dropped) so callers can detect\n * loss without parsing the debug log. */\n droppedEntries: number;\n}\n\n/** Snapshot of an entry handed to `onEvict` when it permanently leaves the\n * reachable stacks. Ops are live references — read `name`/`args`, don't\n * mutate. */\nexport interface EvictedEntry {\n id: number;\n label: string;\n forwardOps: readonly Op[];\n baseOps: readonly Op[];\n}\n\n/** Read-only view of a history entry exposed via `History.entries()`. */\nexport interface HistoryEntry {\n /** Stable monotonic id (preserved across coalesce merges). */\n id: number;\n /** Human-readable label (the `label` arg passed to `applyOps`). */\n label: string;\n /** Push/last-coalesce timestamp (ms). */\n timestamp: number;\n /** How many pushes this entry holds: 1 when it was pushed once, plus one\n * for every later push coalesced into it. */\n pushes: number;\n /** Set of node ids touched by any op in this entry. Populated from ops\n * whose `args` carry an `id` field (transform, setPath, reparent) or a\n * `node.id` field (insert, delete). Ops without a recognisable id field\n * contribute nothing. May be `undefined` for deserialized entries\n * restored from an older snapshot that predates this field. */\n touchedIds?: ReadonlySet<string>;\n /** Selection restored when this entry is undone. */\n selectionBefore?: readonly string[];\n /** Selection restored when this entry is redone. */\n selectionAfter?: readonly string[];\n}\n\n/** One future at the current entry, as `History.branches()` lists it. */\nexport interface HistoryBranch {\n /** The id of the branch's first entry; what `switchBranch` takes. */\n id: number;\n /** The first entry's label and timestamp. */\n label: string;\n timestamp: number;\n /** How many entries run along it from here, nested branches not counted. */\n length: number;\n /** Whether `redo()` would follow it. */\n current: boolean;\n}\n\n/** Op-batched undo/redo controller returned by `createHistory`. */\nexport interface History {\n apply(op: Op, label?: string): void;\n applyOps(ops: Op[], label: string): void;\n undo(): void;\n redo(): void;\n canUndo(): boolean;\n canRedo(): boolean;\n /** Number of entries on the undo stack (O(1); `entries().undo.length`\n * without materializing the views). */\n undoDepth(): number;\n /** Number of entries on the redo stack (O(1)). */\n redoDepth(): number;\n clear(): void;\n /** Snapshot of the current undo + redo stacks. `undo` is oldest→newest\n * (i.e. the last element is what `undo()` would pop next); `redo` is\n * also oldest→newest from the user's perspective (i.e. the *first*\n * element is what `redo()` would pop next — see implementation note).\n * Freshly built on each call, and readonly so that stays true. */\n entries(): { undo: readonly HistoryEntry[]; redo: readonly HistoryEntry[] };\n /** Walk the history forward/back until exactly `n` entries are on the\n * undo stack (0 ≤ n ≤ entries().undo.length + entries().redo.length).\n * Equivalent to repeated `undo()`/`redo()` calls but doesn't bother\n * rebuilding entry snapshots between steps. No-op if already at `n`. */\n goto(n: number): void;\n /** The `timestamp` of entry `i`, counting in time order through the undo\n * stack and on into the redo stack, so `timestampAt(undoDepth())` is the\n * next redo's. `undefined` outside the entries. Allocates nothing. */\n timestampAt(i: number): number | undefined;\n /** The undo depth at which every entry stamped at or before `t` is applied\n * and none after it: `goto(depthAt(t))` seeks the history to `t`, either\n * way. Assumes stamps never decrease in time order, which holds while\n * `now()` does not run backward between pushes. */\n depthAt(t: number): number;\n /** Evict the oldest undo entries stamped before `t`, reporting each through\n * `onEvict` — retention by age, beside `historyLimit`'s by count. Redo\n * entries are never pruned: they are a future still reachable. */\n prune(t: number): void;\n /** The futures that fork from the current entry, oldest first: the redo\n * stack, marked `current`, and any a branching history kept. Empty when\n * there is nothing to redo and nothing was kept. */\n branches(): readonly HistoryBranch[];\n /** Make the branch `id` the redo future, keeping the one it replaces. Applies\n * nothing — both start from where the history stands. Throws when no branch\n * of that id forks from the current entry. */\n switchBranch(id: number): void;\n /** Monotonic counter bumped on every push/undo/redo/clear/coalesce.\n * Cheap to read; callers use it as a React dep to detect changes. */\n getVersion(): number;\n /** End the current coalescing run: the next entry is pushed fresh even if\n * its keys match the top entry and it lands inside the window. For a\n * caller that changed state the stack does not know about, so an entry\n * coalesced across that change would undo past it. */\n seal(): void;\n /** Subscribe to history changes. Fires after every push/undo/redo/\n * clear/coalesce. Returns an unsubscribe fn. */\n subscribe(listener: () => void): () => void;\n /** Snapshot the undo + redo stacks in a structured-clone-safe form.\n * Entries whose ops aren't all kit-registered (i.e. any op missing a\n * `name`) are dropped from the snapshot with a debug-level log — they\n * can't round-trip, so we omit them rather than emit a half-restorable\n * entry. The in-memory stacks aren't modified. */\n serialize(): SerializedHistory;\n /** Replace the current undo + redo stacks with the deserialized contents\n * of `snapshot`. Ops are rebuilt via the `rebuildOp` option when\n * provided, then the global registry; unknown names become no-op\n * placeholders so stack ordering survives across kit-version skew.\n * Bumps `version` and notifies subscribers exactly once. */\n restore(snapshot: SerializedHistory): void;\n /** Push an entry whose ops have already been applied to the adapter.\n * Unlike `applyOps`, does NOT call `op.apply()`. Used by Journal.commit\n * to flush a session's net forward ops to the parent as one entry without\n * re-mutating the scene. */\n recordEntry(ops: Op[], label: string, options?: RecordEntryOptions): void;\n /** Concatenated forwardOps of every undo-stack entry, in order. Snapshot\n * of \"what changes are currently applied via this history\" — useful for\n * Journal.commit to flush to a parent, and for any caller that wants to\n * diff against a baseline. */\n allForwardOps(): Op[];\n /** The id that will be assigned to the *next* pushed entry. Stable\n * monotonic counter; callers use it to tag a fork point (see Journal). */\n currentEntryId(): number;\n /** Open a scoped sub-history. All apply/undo/redo on the returned Journal\n * affect the same adapter; on commit, the Journal's net forward ops are\n * flushed to this History as one entry. See spec docs/superpowers/specs/\n * 2026-05-24-modality-design.md for the full lifecycle. */\n beginJournal(opts: BeginJournalOptions): Journal;\n /** Re-activate a suspended journal. Throws if the journal was committed or\n * cancelled (those are terminal), or if a different journal is currently\n * active — at most one journal writes to the adapter at a time, on resume\n * as well as on open. Staleness checking is the caller's\n * responsibility — consult `journal.forkedAtEntryId` against\n * `currentEntryId()` and your own op-semantic rules to decide whether\n * to resume or discard before calling this. */\n resumeJournal(journal: Journal): void;\n}\n\n/** Read/write access to whatever holds the caller's selection. The engine\n * stores the ids it is handed and hands them back on the way past; it\n * attaches no meaning to them and never records a selection change as an\n * entry of its own. */\nexport interface HistorySelection {\n get(): readonly string[];\n set(ids: readonly string[]): void;\n}\n\n/** Options for `recordEntry`. */\nexport interface RecordEntryOptions {\n /** Selection as of before the already-applied ops ran. `recordEntry` is\n * called after the fact, so the live selection has moved on by then and\n * the engine cannot sample it — a caller that wants undo to restore the\n * selection captures it when the batch opens and passes it here. */\n selectionBefore?: readonly string[];\n}\n\n/** Options for `createHistory`. */\nexport interface CreateHistoryOptions {\n /** Window (ms) within which a new entry may merge into the previous one\n * via matching `Op.coalesceKey`. Defaults to `0` (no coalescing — every\n * `applyOps` pushes a discrete entry). Recommended: ~500ms for typical\n * rapid-input UX (nudge, per-keystroke text edits). The window resets on\n * each successful coalesce, so a sustained burst keeps merging, and closes\n * on any other history operation — only the entry the last push created is\n * ever a merge target. */\n coalesceWindowMs?: number;\n /** Clock injection point for tests. Defaults to `Date.now`. */\n now?: () => number;\n /** Where the caller's selection lives. Supplied, each entry records the\n * selection around it and undo / redo / goto restore it; omitted, the\n * engine never reads or writes selection at all. */\n selection?: HistorySelection;\n /** Maximum undo-stack depth. When a push overflows the cap the oldest\n * entry is evicted (reported via `onEvict`) and can no longer be undone.\n * `0` disables the undo stack entirely — every push is evicted\n * synchronously (negative values are clamped to `0`). Default: unbounded. */\n historyLimit?: number;\n /** Fired once per entry that permanently leaves the reachable stacks:\n * redo entries dropped by a branch edit (a new push or `recordEntry`\n * after undo) and undo entries evicted by `historyLimit`.\n * NOT fired by `clear()` or `restore()` — those wholesale-replace the\n * history and the caller already knows. Note `restore()` does not enforce\n * `historyLimit` either: a restored snapshot may exceed the cap, which\n * re-applies (evicting via `onEvict`) on the next push. */\n onEvict?: (entry: EvictedEntry) => void;\n /** Keep the redo future when a push is made while there is one, as a branch\n * `branches()` lists and `switchBranch` returns to, instead of dropping it.\n * Undo and redo still walk one line, the current branch. An entry evicted by\n * `historyLimit` or `prune` takes the branches forking from it along.\n * Default `false`. */\n branching?: boolean;\n /** Custom op rebuilder consulted by `restore()` before the global\n * op-factory registry. Return `null` to fall through (global registry,\n * then a no-op placeholder). Lets an owner rebuild ops whose handlers\n * live in per-instance state the global registry can't reach (e.g. a\n * Scene's registered op kinds).\n *\n * May be invoked more than once per entry with the same `(name, args)` —\n * once per op for `forwardOps` and again for `baseOps` (the same array\n * until a coalesce splits them). Unlike `onEvict`, a throwing hook is\n * NOT caught: it aborts `restore()` mid-rebuild and can leave the\n * stacks partially rebuilt. */\n rebuildOp?: (name: string, args: unknown) => Op | null;\n /** Diagnostics sink. Omitted, the engine is silent — it deliberately owns no\n * logging utility, so that this package depends on nothing. `@weasel-js/core`'s\n * `createHistory` wrapper routes these into its `debug/flag` namespace, which\n * is what makes `DEBUG=history` work for kit consumers. */\n debug?: HistoryLogger;\n}\n\n/** Diagnostics sink for {@link CreateHistoryOptions.debug}. Messages arrive\n * pre-formatted and unconditional; deciding whether to emit them is the\n * caller's job. */\nexport interface HistoryLogger {\n log(message: string): void;\n warn(message: string): void;\n}\n\n/** Silent default — keeps every call site unconditional. */\nconst SILENT: HistoryLogger = { log: () => {}, warn: () => {} };\n\n/** Build an op-batched undo/redo `History`. The adapter is passed to each op's `apply`/`invert`. */\nexport function createHistory(adapter: unknown, options: CreateHistoryOptions = {}): History {\n const undoStack: Entry[] = [];\n const redoStack: Entry[] = [];\n let activeJournal: Journal | null = null;\n const coalesceWindowMs = options.coalesceWindowMs ?? 0;\n const now = options.now ?? (() => Date.now());\n const historyLimit = Math.max(0, options.historyLimit ?? Infinity);\n const onEvict = options.onEvict;\n const branching = options.branching ?? false;\n /** Futures a push set aside, by the id of the entry they fork from (0 for\n * the start), each in time order. Only a branching history adds to it. */\n const forks = new Map<number, Entry[][]>();\n const customRebuild = options.rebuildOp;\n const selection = options.selection;\n const logger = options.debug ?? SILENT;\n let nextEntryId = 1;\n let version = 0;\n /** `version` as of the last push or coalesce. A batch may only merge into\n * the top entry while this still matches: every other operation bumps\n * `version`, so undo, redo, goto, clear, restore and `recordEntry` all\n * expire the merge window without having to remember to. Without that, an\n * edit made after stepping back rewrites whatever entry the step left on\n * top, and one undo then jumps past it. */\n let coalesceAnchorVersion = -1;\n const listeners = new Set<() => void>();\n function bump(): void {\n version++;\n for (const l of listeners) l();\n }\n\n /** Report entries that just became permanently unreachable. A throwing\n * callback must not desync the stacks mid-mutation, so failures are\n * contained and surfaced via the debug flag. */\n function reportEvicted(entries: Entry[]): void {\n if (!onEvict) return;\n for (const e of entries) {\n try {\n onEvict({ id: e.id, label: e.label, forwardOps: e.forwardOps, baseOps: e.baseOps });\n } catch (err) {\n logger.warn(`onEvict callback threw for entry id=${e.id} \"${e.label}\": ${String(err)}`);\n }\n }\n }\n\n /** Report `entries` evicted, and evict every branch forking from them. */\n function evict(entries: Entry[]): void {\n reportEvicted(entries);\n for (const e of entries) {\n const kept = forks.get(e.id);\n if (!kept) continue;\n forks.delete(e.id);\n for (const branch of kept) evict(branch);\n }\n }\n\n /** The id of the entry the history stands on, 0 at the start. */\n function forkPoint(): number {\n return undoStack[undoStack.length - 1]?.id ?? 0;\n }\n\n /** Clear the redo stack after a push forking from `at`: kept as a branch\n * when branching, else dropped and reported. */\n function retireRedo(at: number): void {\n if (redoStack.length === 0) return;\n const future = redoStack.splice(0);\n if (!branching) return evict(future);\n const kept = forks.get(at);\n if (kept) kept.push(future.reverse());\n else forks.set(at, [future.reverse()]);\n }\n\n /** Evict the oldest undo entries past `historyLimit`, reporting each. */\n function enforceLimit(): void {\n while (undoStack.length > historyLimit) {\n evict([undoStack.shift()!]);\n }\n }\n\n function applyOps(ops: Op[]): void {\n for (const op of ops) op.apply(adapter);\n }\n\n /** Apply each op and collect whether any reported a real mutation.\n * Returns true iff at least one op did NOT explicitly return `false` /\n * `'noop'`. Used by `pushOrCoalesce` to skip pushing entries when every\n * op in the batch was a silent no-op (e.g. reorder where the order\n * already matched). Existing ops that return `undefined`/`void` count\n * as \"mutated\" — the default — so this is backwards-compatible. */\n function applyOpsAndDetectMutation(ops: Op[]): boolean {\n let anyMutated = false;\n for (const op of ops) {\n const r = op.apply(adapter);\n if (r !== false && r !== 'noop') anyMutated = true;\n }\n return anyMutated;\n }\n\n function invertEntry(entry: Entry): Op[] {\n return [...entry.baseOps].reverse().map((op) => op.invert());\n }\n\n /** Copy on read: the caller's array is theirs to mutate. */\n function readSelection(): readonly string[] | undefined {\n return selection ? [...selection.get()] : undefined;\n }\n\n /** Walk one entry backwards. The selection is restored *after* the\n * inverted ops apply, so an entry that also carries a `setSelection`-style\n * op can't overwrite the snapshot. */\n function stepBack(entry: Entry): void {\n const leaving = readSelection();\n if (leaving) entry.selectionAfter = leaving;\n applyOps(invertEntry(entry));\n if (selection && entry.selectionBefore) selection.set(entry.selectionBefore);\n }\n\n /** Walk one entry forwards. Mirror of `stepBack`. */\n function stepForward(entry: Entry): void {\n const leaving = readSelection();\n if (leaving) entry.selectionBefore = leaving;\n applyOps(entry.forwardOps);\n if (selection && entry.selectionAfter) selection.set(entry.selectionAfter);\n }\n\n /** Coalesce eligibility: every op on both sides has a `coalesceKey`, and\n * the multisets of keys match (order-independent). Match by multiset\n * rather than positional index so a multi-id selection can re-emit ops in\n * any order between batches without breaking the merge. */\n function canCoalesce(top: Entry, incoming: Op[]): boolean {\n if (coalesceWindowMs <= 0) return false;\n if (version !== coalesceAnchorVersion) return false;\n if (now() - top.timestamp > coalesceWindowMs) return false;\n if (top.forwardOps.length === 0 || incoming.length === 0) return false;\n if (top.forwardOps.length !== incoming.length) return false;\n const counts = new Map<string, number>();\n for (const op of top.forwardOps) {\n const k = op.coalesceKey;\n if (k === undefined) return false;\n counts.set(k, (counts.get(k) ?? 0) + 1);\n }\n for (const op of incoming) {\n const k = op.coalesceKey;\n if (k === undefined) return false;\n const c = counts.get(k);\n if (!c) return false;\n counts.set(k, c - 1);\n }\n return true;\n }\n\n function pushOrCoalesce(ops: Op[], label: string): void {\n if (ops.length === 0) return;\n const selectionBefore = readSelection();\n const anyMutated = applyOpsAndDetectMutation(ops);\n if (!anyMutated) {\n // Every op reported `false`/`'noop'`. Skip the push so undo stays\n // tied to real state changes. Surfaced through the kit's debug\n // flag so the upstream caller can consider avoiding the dispatch\n // entirely. Hidden by default; enable via\n // `localStorage.setItem('weasel.debug', '1')`.\n logger.warn(\n `'${label}' batch was a no-op — every op reported false/'noop'. ` +\n `Skipping the undo entry; consider gating the dispatch upstream to avoid the wasted work.`,\n );\n return;\n }\n const incoming = touchedIdsFromOps(ops);\n const top = undoStack[undoStack.length - 1];\n if (top && canCoalesce(top, ops)) {\n top.forwardOps = ops;\n top.timestamp = now();\n top.pushes++;\n // Merge incoming touched ids into the coalesced entry's set.\n if (incoming.size > 0) {\n const merged = new Set(top.touchedIds);\n for (const id of incoming) merged.add(id);\n top.touchedIds = merged;\n }\n // baseOps + label + id intentionally preserved — undo returns to the\n // pre-edit state, the original label sticks, and the entry id stays\n // stable so React lists keyed on id don't flicker. No dropRedo() here:\n // the anchor only survives until the stack is stepped, and every step\n // clears it, so a live anchor implies an empty redo stack.\n logger.log(`coalesce '${label}' into entry id=${top.id} (${ops.length} ops)`);\n bump();\n coalesceAnchorVersion = version;\n return;\n }\n logger.log(`push '${label}' (${ops.length} ops)`);\n const at = forkPoint();\n undoStack.push({\n id: nextEntryId++, forwardOps: ops, baseOps: ops, label, timestamp: now(), pushes: 1, touchedIds: incoming,\n ...(selectionBefore ? { selectionBefore } : {}),\n });\n retireRedo(at);\n enforceLimit();\n bump();\n coalesceAnchorVersion = version;\n }\n\n return {\n apply(op, label) {\n pushOrCoalesce([op], label ?? op.label ?? '');\n },\n applyOps(ops, label) {\n pushOrCoalesce(ops, label);\n },\n undo() {\n const entry = undoStack.pop();\n if (!entry) return;\n stepBack(entry);\n redoStack.push(entry);\n bump();\n },\n redo() {\n const entry = redoStack.pop();\n if (!entry) return;\n stepForward(entry);\n undoStack.push(entry);\n bump();\n },\n seal() {\n coalesceAnchorVersion = -1;\n },\n canUndo: () => undoStack.length > 0,\n canRedo: () => redoStack.length > 0,\n undoDepth: () => undoStack.length,\n redoDepth: () => redoStack.length,\n clear: () => {\n const had = undoStack.length > 0 || redoStack.length > 0 || forks.size > 0;\n undoStack.length = 0;\n redoStack.length = 0;\n forks.clear();\n if (had) bump();\n },\n entries() {\n const toView = (e: Entry): HistoryEntry => ({\n id: e.id, label: e.label, timestamp: e.timestamp, pushes: e.pushes, touchedIds: e.touchedIds,\n ...(e.selectionBefore ? { selectionBefore: e.selectionBefore } : {}),\n ...(e.selectionAfter ? { selectionAfter: e.selectionAfter } : {}),\n });\n // redoStack is internally stored newest-on-top (so `pop()` redoes the\n // next-most-recent undo). Reverse on the way out so callers see the\n // entries in chronological order — the user's next redo is the first\n // element, matching `entries().redo[0]` semantics.\n return {\n undo: undoStack.map(toView),\n redo: [...redoStack].reverse().map(toView),\n };\n },\n goto(n) {\n // Total length stays constant during this walk (we only shuffle\n // entries between undo and redo stacks).\n const total = undoStack.length + redoStack.length;\n if (n < 0 || n > total || n === undoStack.length) return;\n while (undoStack.length > n) {\n const entry = undoStack.pop()!;\n stepBack(entry);\n redoStack.push(entry);\n }\n while (undoStack.length < n) {\n const entry = redoStack.pop();\n if (!entry) break; // defensive — shouldn't fire given the bounds check above\n stepForward(entry);\n undoStack.push(entry);\n }\n bump();\n },\n timestampAt(i) {\n if (i < 0) return undefined;\n if (i < undoStack.length) return undoStack[i]!.timestamp;\n // redoStack is newest-on-top, so time order runs from its end.\n return redoStack[redoStack.length - 1 - (i - undoStack.length)]?.timestamp;\n },\n depthAt(t) {\n let n = undoStack.length;\n while (n > 0 && undoStack[n - 1]!.timestamp > t) n--;\n if (n < undoStack.length) return n;\n for (let r = redoStack.length - 1; r >= 0 && redoStack[r]!.timestamp <= t; r--) n++;\n return n;\n },\n prune(t) {\n let k = 0;\n while (k < undoStack.length && undoStack[k]!.timestamp < t) k++;\n if (k === 0) return;\n evict(undoStack.splice(0, k));\n bump();\n },\n branches() {\n const view = (entries: readonly Entry[], current: boolean): HistoryBranch => {\n const first = entries[0]!;\n return { id: first.id, label: first.label, timestamp: first.timestamp, length: entries.length, current };\n };\n const out = (forks.get(forkPoint()) ?? []).map((b) => view(b, false));\n if (redoStack.length > 0) out.push(view([...redoStack].reverse(), true));\n return out.sort((a, b) => a.id - b.id);\n },\n switchBranch(id) {\n if (redoStack[redoStack.length - 1]?.id === id) return;\n const at = forkPoint();\n const kept = forks.get(at) ?? [];\n const i = kept.findIndex((b) => b[0]!.id === id);\n if (i < 0) throw new Error(`No branch ${id} forks from entry ${at}`);\n const [chosen] = kept.splice(i, 1);\n if (redoStack.length > 0) kept.push(redoStack.splice(0).reverse());\n if (kept.length > 0) forks.set(at, kept);\n else forks.delete(at);\n redoStack.push(...[...chosen!].reverse());\n bump();\n },\n getVersion: () => version,\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n serialize(): SerializedHistory {\n let dropped = 0;\n const project = (e: Entry): SerializedHistoryEntry | null => {\n const s = entryToSerial(e, logger);\n if (s === null) dropped++;\n return s;\n };\n return {\n version: 1,\n undoStack: undoStack.map(project).filter((e): e is SerializedHistoryEntry => e !== null),\n redoStack: redoStack.map(project).filter((e): e is SerializedHistoryEntry => e !== null),\n ...(forks.size > 0 ? {\n branches: [...forks].flatMap(([at, kept]) => kept.map((b) => ({\n at,\n entries: b.map(project).filter((e): e is SerializedHistoryEntry => e !== null),\n }))).filter((b) => b.entries.length > 0),\n } : {}),\n nextEntryId,\n droppedEntries: dropped,\n };\n },\n recordEntry(ops: Op[], label: string, options: RecordEntryOptions = {}): void {\n if (ops.length === 0) return;\n const at = forkPoint();\n undoStack.push({\n id: nextEntryId++, forwardOps: ops, baseOps: ops, label, timestamp: now(), pushes: 1,\n touchedIds: touchedIdsFromOps(ops),\n ...(options.selectionBefore ? { selectionBefore: [...options.selectionBefore] } : {}),\n });\n retireRedo(at);\n enforceLimit();\n bump();\n },\n allForwardOps(): Op[] {\n const out: Op[] = [];\n for (const e of undoStack) {\n for (const op of e.forwardOps) out.push(op);\n }\n return out;\n },\n currentEntryId(): number {\n return nextEntryId;\n },\n beginJournal(opts: BeginJournalOptions): Journal {\n if (activeJournal !== null && activeJournal.isActive()) {\n throw new Error('A journal is already active — commit, cancel, or suspend it first');\n }\n // `adapter` is the closure-captured adapter passed to createHistory.\n // The returned History object's `this` doesn't carry it, so we pass\n // it through to the factory directly.\n const j = createJournalInternal(this, adapter, opts, () => { activeJournal = null; }, {\n coalesceWindowMs, now, debug: logger,\n ...(selection ? { selection } : {}),\n });\n activeJournal = j;\n return j;\n },\n resumeJournal(journal: Journal): void {\n if (activeJournal !== null && activeJournal !== journal && activeJournal.isActive()) {\n throw new Error('A journal is already active — commit, cancel, or suspend it first');\n }\n _resumeJournalInternal(journal);\n activeJournal = journal;\n },\n restore(snapshot: SerializedHistory): void {\n undoStack.length = 0;\n redoStack.length = 0;\n for (const se of snapshot.undoStack) {\n undoStack.push(serialToEntry(se, customRebuild, logger));\n }\n for (const se of snapshot.redoStack) {\n redoStack.push(serialToEntry(se, customRebuild, logger));\n }\n forks.clear();\n for (const b of snapshot.branches ?? []) {\n const entries = b.entries.map((se) => serialToEntry(se, customRebuild, logger));\n const kept = forks.get(b.at);\n if (kept) kept.push(entries);\n else forks.set(b.at, [entries]);\n }\n // Seed nextEntryId from the snapshot, then defensively bump past any\n // restored id — a malformed snapshot with duplicate or out-of-range\n // ids should never produce a collision with future entries.\n nextEntryId = snapshot.nextEntryId;\n for (const e of undoStack) if (e.id >= nextEntryId) nextEntryId = e.id + 1;\n for (const e of redoStack) if (e.id >= nextEntryId) nextEntryId = e.id + 1;\n for (const kept of forks.values()) for (const b of kept) for (const e of b) if (e.id >= nextEntryId) nextEntryId = e.id + 1;\n bump();\n },\n };\n}\n\n/** Extract node ids from an op's `args`. Handles the common patterns:\n * - `args.id` (string) — transform, setPath, reparent\n * - `args.node.id` (string) — insert, delete\n * Ops that don't match either pattern contribute nothing.\n * Exported for in-package test use; not part of the published package API. */\nexport function touchedIdsFromOps(ops: Op[]): ReadonlySet<string> {\n const ids = new Set<string>();\n for (const op of ops) {\n if (op.args === null || typeof op.args !== 'object') continue;\n const a = op.args as Record<string, unknown>;\n if (typeof a['id'] === 'string') {\n ids.add(a['id']);\n } else if (a['node'] !== null && typeof a['node'] === 'object') {\n const n = a['node'] as Record<string, unknown>;\n if (typeof n['id'] === 'string') ids.add(n['id']);\n }\n }\n return ids;\n}\n\n/** Project an `Op` to its `(name, args)` wire form. Returns `null` for ops\n * missing `name` — the caller drops the containing entry. */\nfunction opToSerial(op: Op): SerializedOp | null {\n if (typeof op.name !== 'string') return null;\n return { name: op.name, args: op.args };\n}\n\n/** Project a runtime entry to its serialized form, or `null` if any op in\n * the entry can't be serialized (we drop the whole entry then — a partially\n * serializable entry would invert against the wrong baseline on undo). */\nfunction entryToSerial(e: Entry, logger: HistoryLogger): SerializedHistoryEntry | null {\n const forwardOps: SerializedOp[] = [];\n for (const op of e.forwardOps) {\n const s = opToSerial(op);\n if (s === null) {\n logger.log(`serialize: dropping entry id=${e.id} \"${e.label}\" — forwardOp without name`);\n return null;\n }\n forwardOps.push(s);\n }\n const baseOps: SerializedOp[] = [];\n for (const op of e.baseOps) {\n const s = opToSerial(op);\n if (s === null) {\n logger.log(`serialize: dropping entry id=${e.id} \"${e.label}\" — baseOp without name`);\n return null;\n }\n baseOps.push(s);\n }\n return {\n id: e.id, label: e.label, forwardOps, baseOps, pushes: e.pushes, timestamp: e.timestamp,\n ...(e.selectionBefore ? { selectionBefore: e.selectionBefore } : {}),\n ...(e.selectionAfter ? { selectionAfter: e.selectionAfter } : {}),\n };\n}\n\n/** Placeholder op used when the registry lacks the requested name. Stable\n * identity (each placeholder is its own invert) keeps undo/redo plumbing\n * happy without performing any adapter mutation. */\nfunction placeholderOp(name: string, args: unknown, label?: string): Op {\n const op: Op = {\n name,\n args,\n label,\n apply: () => 'noop' as const,\n invert: () => op,\n };\n return op;\n}\n\n/** Signature of a per-instance op rebuilder (`CreateHistoryOptions.rebuildOp`). */\ntype CustomRebuild = (name: string, args: unknown) => Op | null;\n\n/** Rebuild a single serialized op via `custom` (if provided), falling back to\n * a no-op placeholder.\n *\n * This engine deliberately knows nothing about any op registry: hydrating a\n * `(name, args)` pair back into an op is the caller's concern, injected\n * through `CreateHistoryOptions.rebuildOp`. `@weasel-js/core`'s\n * `createHistory` wrapper supplies its global op-factory registry as that\n * hook, so core consumers see unchanged behavior. */\nfunction rebuildSerialOp(so: SerializedOp, label: string, custom: CustomRebuild | undefined, logger: HistoryLogger): Op {\n const viaCustom = custom ? custom(so.name, so.args) : null;\n if (viaCustom !== null) return viaCustom;\n logger.log(`restore: unknown op name \"${so.name}\" — substituting no-op placeholder`);\n return placeholderOp(so.name, so.args, label);\n}\n\n/** Rebuild a runtime entry from its serialized form. Unknown op names become\n * no-op placeholders so the entry still occupies its slot in the stack. */\nfunction serialToEntry(se: SerializedHistoryEntry, custom: CustomRebuild | undefined, logger: HistoryLogger): Entry {\n const forwardOps = se.forwardOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));\n const baseOps = se.baseOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));\n return {\n id: se.id,\n label: se.label,\n forwardOps,\n baseOps,\n timestamp: se.timestamp ?? 0,\n pushes: se.pushes ?? 1,\n // Re-derive touchedIds from the rebuilt ops rather than trying to\n // round-trip the Set through the serialized form (Sets aren't JSON-safe).\n touchedIds: touchedIdsFromOps(forwardOps),\n ...(se.selectionBefore ? { selectionBefore: [...se.selectionBefore] } : {}),\n ...(se.selectionAfter ? { selectionAfter: [...se.selectionAfter] } : {}),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/journal.ts","../src/serialized.ts","../src/history.ts","../src/swapOp.ts"],"names":["snapshot","options"],"mappings":"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","file":"index.js","sourcesContent":["import type { Op } from './op';\nimport { _journalAccessInternal, createHistory, type CreateHistoryOptions, type History, type HistoryEntry } from './history';\n\nconst RESUMERS = new WeakMap<Journal, () => void>();\n\n/** Called by `history.resumeJournal`. Not part of the public API. */\nexport function _resumeJournalInternal(j: Journal): void {\n const r = RESUMERS.get(j);\n if (!r) throw new Error('Journal is not resumable (already committed or cancelled)');\n r();\n}\n\n/** Options for `history.beginJournal()`. */\nexport interface BeginJournalOptions {\n /** Label for the single parent-history entry the journal flushes on commit. */\n label: string;\n /** Caller-supplied tag naming what this journal is scoped to — typically the\n * id of the node being edited. The history layer only carries it; callers\n * read it back off the journal to decide whether a suspended journal\n * matches what they are about to edit. */\n targetId?: string;\n /** Coalesce window for the journal's own entries, as\n * `CreateHistoryOptions.coalesceWindowMs`. Defaults to the parent's. */\n coalesceWindowMs?: number;\n}\n\n/**\n * A scoped sub-history forked from a `History`, opened by\n * `history.beginJournal()`. Applies, undoes and redoes against the same\n * adapter as its parent, but keeps its entries to itself: `commit` flushes the\n * journal's applied ops to the parent as one entry, `cancel` rewinds them\n * and contributes nothing. Use it when a self-contained editing session (a\n * text edit, a modal drag) should collapse to a single step in the parent's\n * undo stack while still offering undo *within* the session.\n *\n * A journal is active, suspended or closed. `commit` and `cancel` are\n * terminal; `suspend` lets the parent be used again and can be reversed with\n * `history.resumeJournal()`. Every mutating method throws when the journal is\n * not active.\n */\nexport interface Journal {\n readonly targetId: string | undefined;\n readonly forkedAtEntryId: number;\n\n // Same operational surface as History\n applyBatch(ops: Op[], label: string): void;\n undo(): void;\n redo(): void;\n canUndo(): boolean;\n canRedo(): boolean;\n entries(): { undo: readonly HistoryEntry[]; redo: readonly HistoryEntry[] };\n /** As `History.seal`, for the journal's own entries. */\n seal(): void;\n /** Counter bumped on every change a reader of this journal could see: an\n * apply, undo, redo or coalesce inside it, and every lifecycle transition\n * (`isActive` changes). Pairs with `subscribe` for `useSyncExternalStore`. */\n getVersion(): number;\n /** Fires whenever `getVersion` bumps. Returns an unsubscribe fn. */\n subscribe(listener: () => void): () => void;\n\n // Lifecycle\n commit(label: string): void;\n cancel(): void;\n suspend(): void;\n isActive(): boolean;\n}\n\n/** Internal factory used by `createHistory`'s `beginJournal` method.\n * Not exported via the package's `index.ts` — callers go through\n * `history.beginJournal()`. */\nexport function createJournalInternal(\n parent: History,\n adapter: unknown,\n opts: BeginJournalOptions,\n onClose?: () => void,\n inherited: Pick<CreateHistoryOptions, 'coalesceWindowMs' | 'now' | 'debug' | 'selection'> = {},\n): Journal {\n const inner = createHistory(adapter, {\n ...inherited,\n ...(opts.coalesceWindowMs !== undefined ? { coalesceWindowMs: opts.coalesceWindowMs } : {}),\n });\n const selection = inherited.selection;\n let version = 0;\n const listeners = new Set<() => void>();\n function bump(): void {\n version++;\n for (const l of listeners) l();\n }\n inner.subscribe(bump);\n const forkedAtEntryId = parent.currentEntryId();\n // The whole session collapses to one parent entry, so the selection that\n // entry restores is the one the session opened under — not whatever the\n // last keystroke left.\n const selectionBefore = selection ? [...selection.get()] : undefined;\n type State = 'active' | 'suspended' | 'closed';\n let state: State = 'active';\n const targetId = opts.targetId;\n\n const journal: Journal = {\n targetId,\n forkedAtEntryId,\n\n applyBatch(ops: Op[], label: string): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.applyOps(ops, label);\n },\n undo(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.undo();\n },\n redo(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.redo();\n },\n canUndo(): boolean {\n return inner.canUndo();\n },\n canRedo(): boolean {\n return inner.canRedo();\n },\n entries() {\n return inner.entries();\n },\n seal(): void {\n inner.seal();\n },\n getVersion: () => version,\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n commit(label: string): void {\n if (state !== 'active') throw new Error('Journal is not active');\n // Coalescing splits an entry in two: the forward half is the latest\n // push, the base half the first, and undo inverts the base.\n const { forwardOps, baseOps } = _journalAccessInternal(inner).appliedOps();\n _journalAccessInternal(parent).record(\n forwardOps, baseOps, label, selectionBefore ? { selectionBefore } : undefined,\n );\n state = 'closed';\n RESUMERS.delete(journal);\n onClose?.();\n bump();\n },\n cancel(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n inner.goto(0);\n state = 'closed';\n RESUMERS.delete(journal);\n onClose?.();\n bump();\n },\n suspend(): void {\n if (state !== 'active') throw new Error('Journal is not active');\n state = 'suspended';\n onClose?.();\n bump();\n },\n isActive(): boolean {\n return state === 'active';\n },\n };\n\n RESUMERS.set(journal, () => {\n if (state !== 'suspended') throw new Error('Journal is not suspended');\n state = 'active';\n bump();\n });\n\n return journal;\n}\n","/**\n * A history's entries, their wire form, and the trip between the two.\n */\nimport type { Op } from './op';\n\nexport interface Entry {\n /** Monotonic id assigned at first push. Stable across coalesce merges\n * (a merged entry keeps the original id) so UI lists keyed on `id` don't\n * flicker when the underlying entry mutates. */\n id: number;\n /** Forward ops — applied on redo, reflect the latest to-state after any\n * coalescing. Diverges from `baseOps` only after a coalesce. */\n forwardOps: Op[];\n /** Original ops at first push — their `.invert()` is what undo replays.\n * Preserved across coalesces so undo always returns to the original\n * pre-edit state, no matter how many coalesces happened. */\n baseOps: Op[];\n label: string;\n /** `now()` at last push or coalesce: gates the coalesce window, and is the\n * entry's place in time for `depthAt` and `prune`. */\n timestamp: number;\n /** See `HistoryEntry.pushes`. */\n pushes: number;\n /** Node ids touched by ops in this entry. See `HistoryEntry.touchedIds`. */\n touchedIds: ReadonlySet<string>;\n /** Selection as of just before this entry's ops ran; restored by undo.\n * Undefined when nothing supplied one (no `selection` option, or a\n * `recordEntry` call that captured none). */\n selectionBefore?: readonly string[];\n /** Selection as of just before this entry was last undone; restored by\n * redo. Written on the way past, so it reflects where the user actually\n * was rather than where the entry's ops left them. */\n selectionAfter?: readonly string[];\n}\n\n/** Wire form of a single op inside a serialized history. The pair\n * `(name, args)` reconstructs a live `Op` via the op-factory registry. */\nexport interface SerializedOp {\n name: string;\n args: unknown;\n}\n\n/** Wire form of one history entry. `forwardOps` / `baseOps` mirror the\n * in-memory entry's fields (see `Entry` above) but only carry the\n * serializable `(name, args)` projection of each op. */\nexport interface SerializedHistoryEntry {\n id: number;\n label: string;\n forwardOps: SerializedOp[];\n baseOps: SerializedOp[];\n /** See `HistoryEntry.pushes`. Absent in snapshots that predate it; read as 1. */\n pushes?: number;\n /** See `HistoryEntry.timestamp`. Absent in snapshots that predate it; read as 0. */\n timestamp?: number;\n selectionBefore?: readonly string[];\n selectionAfter?: readonly string[];\n}\n\n/** Snapshot of an entire `History` instance. Designed to live alongside the\n * scene snapshot so a reload restores the undo / redo stacks to exactly\n * where they were. Op `args` are carried by reference, so the snapshot is\n * as serializable as they are: JSON drops a function an op's args hold,\n * where a structured clone (IndexedDB, `postMessage`) throws on it. */\nexport interface SerializedHistory {\n version: 1;\n undoStack: SerializedHistoryEntry[];\n /** Stored newest-first, mirroring the in-memory stack so a deserialized\n * history matches the original's `entries().redo` ordering. */\n redoStack: SerializedHistoryEntry[];\n nextEntryId: number;\n /** Futures kept by a branching history, each under the id of the entry it\n * forks from (`floor`'s for the oldest reachable state), entries in time\n * order. Absent when none. */\n branches?: { at: number; entries: SerializedHistoryEntry[] }[];\n /** The id naming the state beneath the undo stack: that of the last undo\n * entry evicted or left out of the snapshot. Absent, and read as 0, when\n * the stack reaches back to the start. */\n floor?: number;\n /** Entries left out: those with an op lacking a `name`, which can't\n * round-trip through the op-factory registry, and those only reachable\n * across one. Always present (zero when nothing was dropped) so callers\n * can detect loss without parsing the debug log. */\n droppedEntries: number;\n}\n\n/** Diagnostics sink for {@link CreateHistoryOptions.debug}. Messages arrive\n * pre-formatted and unconditional; deciding whether to emit them is the\n * caller's job. */\nexport interface HistoryLogger {\n log(message: string): void;\n warn(message: string): void;\n}\n\n/** The stacks a history holds, as `snapshot` reads them and `rebuilt` answers. */\nexport interface Stacks {\n undoStack: Entry[];\n redoStack: Entry[];\n /** Futures set aside, by the id of the entry they fork from. */\n forks: Map<number, Entry[][]>;\n floorId: number;\n nextEntryId: number;\n}\n\n/** `stacks` as a snapshot: see `History.serialize` for what is left out. */\nexport function snapshot(stacks: Stacks, logger: HistoryLogger): SerializedHistory {\n const { undoStack, redoStack, forks, floorId, nextEntryId } = stacks;\n let total = 0;\n const project = (entries: readonly Entry[]): (SerializedHistoryEntry | null)[] => {\n total += entries.length;\n return entries.map((e) => entryToSerial(e, logger));\n };\n // Both stacks keep the entries nearest the current state at their end,\n // so what survives a nameless entry is whatever follows the last one.\n const nearest = (stack: readonly Entry[]): SerializedHistoryEntry[] => {\n const serial = project(stack);\n return serial.slice(serial.lastIndexOf(null) + 1) as SerializedHistoryEntry[];\n };\n const undo = nearest(undoStack);\n const redo = nearest(redoStack);\n\n // Cutting the undo stack raises the snapshot's floor to the last entry cut.\n const floor = undo.length === undoStack.length ? floorId : undoStack[undoStack.length - undo.length - 1]!.id;\n const reachable = new Set<number>([floor, ...[...undo, ...redo].map((e) => e.id)]);\n let pending = [...forks].flatMap(([at, kept]) => kept.map((b) => {\n const serial = project(b);\n const cut = serial.indexOf(null);\n return { at, entries: (cut < 0 ? serial : serial.slice(0, cut)) as SerializedHistoryEntry[] };\n })).filter((b) => b.entries.length > 0);\n const branches: { at: number; entries: SerializedHistoryEntry[] }[] = [];\n for (let grew = true; grew; ) {\n grew = false;\n const waiting: typeof pending = [];\n for (const b of pending) {\n if (!reachable.has(b.at)) { waiting.push(b); continue; }\n branches.push(b);\n for (const e of b.entries) reachable.add(e.id);\n grew = true;\n }\n pending = waiting;\n }\n\n const carried = undo.length + redo.length + branches.reduce((n, b) => n + b.entries.length, 0);\n return {\n version: 1,\n undoStack: undo,\n redoStack: redo,\n ...(branches.length > 0 ? { branches } : {}),\n ...(floor !== 0 ? { floor } : {}),\n nextEntryId,\n droppedEntries: total - carried,\n };\n}\n\n/** The stacks `snapshot` describes, each op rebuilt through `custom`. */\nexport function rebuilt(snapshot: SerializedHistory, custom: CustomRebuild | undefined, logger: HistoryLogger): Stacks {\n const entry = (se: SerializedHistoryEntry): Entry => serialToEntry(se, custom, logger);\n const undoStack = snapshot.undoStack.map(entry);\n const redoStack = snapshot.redoStack.map(entry);\n const forks = new Map<number, Entry[][]>();\n for (const b of snapshot.branches ?? []) {\n const entries = b.entries.map(entry);\n const kept = forks.get(b.at);\n if (kept) kept.push(entries);\n else forks.set(b.at, [entries]);\n }\n // Seed nextEntryId from the snapshot, then defensively bump past any\n // restored id — a malformed snapshot with duplicate or out-of-range\n // ids should never produce a collision with future entries.\n let nextEntryId = snapshot.nextEntryId;\n const seen = [undoStack, redoStack, ...[...forks.values()].flat()];\n for (const entries of seen) for (const e of entries) if (e.id >= nextEntryId) nextEntryId = e.id + 1;\n return { undoStack, redoStack, forks, floorId: snapshot.floor ?? 0, nextEntryId };\n}\n\n/** Extract node ids from an op's `args`. Handles the common patterns:\n * - `args.id` (string) — transform, setPath, reparent\n * - `args.node.id` (string) — insert, delete\n * - `args.nodes[].id` (string) — an op that takes several nodes at once\n * Ops that match none of them contribute nothing.\n * Exported for in-package test use; not part of the published package API. */\nexport function touchedIdsFromOps(ops: Op[]): ReadonlySet<string> {\n const ids = new Set<string>();\n for (const op of ops) {\n if (op.args === null || typeof op.args !== 'object') continue;\n const a = op.args as Record<string, unknown>;\n if (typeof a['id'] === 'string') {\n ids.add(a['id']);\n } else if (a['node'] !== null && typeof a['node'] === 'object') {\n const n = a['node'] as Record<string, unknown>;\n if (typeof n['id'] === 'string') ids.add(n['id']);\n } else if (Array.isArray(a['nodes'])) {\n for (const n of a['nodes'] as unknown[]) {\n const id = n !== null && typeof n === 'object' ? (n as Record<string, unknown>)['id'] : undefined;\n if (typeof id === 'string') ids.add(id);\n }\n }\n }\n return ids;\n}\n\n/** Project an `Op` to its `(name, args)` wire form. Returns `null` for ops\n * missing `name` — the caller drops the containing entry. */\nfunction opToSerial(op: Op): SerializedOp | null {\n if (typeof op.name !== 'string') return null;\n return { name: op.name, args: op.args };\n}\n\n/** Project a runtime entry to its serialized form, or `null` if any op in\n * the entry can't be serialized (we drop the whole entry then — a partially\n * serializable entry would invert against the wrong baseline on undo). */\nfunction entryToSerial(e: Entry, logger: HistoryLogger): SerializedHistoryEntry | null {\n const forwardOps: SerializedOp[] = [];\n for (const op of e.forwardOps) {\n const s = opToSerial(op);\n if (s === null) {\n logger.log(`serialize: dropping entry id=${e.id} \"${e.label}\" — forwardOp without name`);\n return null;\n }\n forwardOps.push(s);\n }\n const baseOps: SerializedOp[] = [];\n for (const op of e.baseOps) {\n const s = opToSerial(op);\n if (s === null) {\n logger.log(`serialize: dropping entry id=${e.id} \"${e.label}\" — baseOp without name`);\n return null;\n }\n baseOps.push(s);\n }\n return {\n id: e.id, label: e.label, forwardOps, baseOps, pushes: e.pushes, timestamp: e.timestamp,\n ...(e.selectionBefore ? { selectionBefore: e.selectionBefore } : {}),\n ...(e.selectionAfter ? { selectionAfter: e.selectionAfter } : {}),\n };\n}\n\n/** Placeholder op used when the registry lacks the requested name. Stable\n * identity (each placeholder is its own invert) keeps undo/redo plumbing\n * happy without performing any adapter mutation. */\nfunction placeholderOp(name: string, args: unknown, label?: string): Op {\n const op: Op = {\n name,\n args,\n label,\n apply: () => 'noop' as const,\n invert: () => op,\n };\n return op;\n}\n\n/** Signature of a per-instance op rebuilder (`CreateHistoryOptions.rebuildOp`). */\ntype CustomRebuild = (name: string, args: unknown) => Op | null;\n\n/** Rebuild a single serialized op via `custom` (if provided), falling back to\n * a no-op placeholder.\n *\n * This engine deliberately knows nothing about any op registry: hydrating a\n * `(name, args)` pair back into an op is the caller's concern, injected\n * through `CreateHistoryOptions.rebuildOp`. `@weasel-js/core`'s\n * `createHistory` wrapper supplies its global op-factory registry as that\n * hook, so core consumers see unchanged behavior. */\nfunction rebuildSerialOp(so: SerializedOp, label: string, custom: CustomRebuild | undefined, logger: HistoryLogger): Op {\n const viaCustom = custom ? custom(so.name, so.args) : null;\n if (viaCustom !== null) return viaCustom;\n logger.log(`restore: unknown op name \"${so.name}\" — substituting no-op placeholder`);\n return placeholderOp(so.name, so.args, label);\n}\n\n/** Rebuild a runtime entry from its serialized form. Unknown op names become\n * no-op placeholders so the entry still occupies its slot in the stack. */\nfunction serialToEntry(se: SerializedHistoryEntry, custom: CustomRebuild | undefined, logger: HistoryLogger): Entry {\n const forwardOps = se.forwardOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));\n const baseOps = se.baseOps.map((so) => rebuildSerialOp(so, se.label, custom, logger));\n return {\n id: se.id,\n label: se.label,\n forwardOps,\n baseOps,\n timestamp: se.timestamp ?? 0,\n pushes: se.pushes ?? 1,\n // Re-derive touchedIds from the rebuilt ops rather than trying to\n // round-trip the Set through the serialized form (Sets aren't JSON-safe).\n touchedIds: touchedIdsFromOps(forwardOps),\n ...(se.selectionBefore ? { selectionBefore: [...se.selectionBefore] } : {}),\n ...(se.selectionAfter ? { selectionAfter: [...se.selectionAfter] } : {}),\n };\n}\n","import type { Op } from './op';\nimport { createJournalInternal, _resumeJournalInternal, type Journal, type BeginJournalOptions } from './journal';\nimport {\n rebuilt, snapshot, touchedIdsFromOps,\n type Entry, type HistoryLogger, type SerializedHistory,\n} from './serialized';\n\nexport { touchedIdsFromOps } from './serialized';\nexport type { HistoryLogger, SerializedHistory, SerializedHistoryEntry, SerializedOp } from './serialized';\n\n/** Snapshot of an entry handed to `onEvict` when it permanently leaves the\n * reachable stacks. Ops are live references — read `name`/`args`, don't\n * mutate. */\nexport interface EvictedEntry {\n id: number;\n label: string;\n forwardOps: readonly Op[];\n baseOps: readonly Op[];\n}\n\n/** Read-only view of a history entry exposed via `History.entries()`. */\nexport interface HistoryEntry {\n /** Stable monotonic id (preserved across coalesce merges). */\n id: number;\n /** Human-readable label (the `label` arg passed to `applyOps`). */\n label: string;\n /** Push/last-coalesce timestamp (ms). */\n timestamp: number;\n /** How many pushes this entry holds: 1 when it was pushed once, plus one\n * for every later push coalesced into it. */\n pushes: number;\n /** Set of node ids touched by any op in this entry. Populated from ops\n * whose `args` carry an `id` field (transform, setPath, reparent) or a\n * `node.id` field (insert, delete). Ops without a recognisable id field\n * contribute nothing. May be `undefined` for deserialized entries\n * restored from an older snapshot that predates this field. */\n touchedIds?: ReadonlySet<string>;\n /** Selection restored when this entry is undone. */\n selectionBefore?: readonly string[];\n /** Selection restored when this entry is redone. */\n selectionAfter?: readonly string[];\n}\n\n/** One future at the current entry, as `History.branches()` lists it. */\nexport interface HistoryBranch {\n /** The id of the branch's first entry; what `switchBranch` takes. */\n id: number;\n /** The first entry's label and timestamp. */\n label: string;\n timestamp: number;\n /** How many entries run along it from here, nested branches not counted. */\n length: number;\n /** Whether `redo()` would follow it. */\n current: boolean;\n}\n\n/** Op-batched undo/redo controller returned by `createHistory`. */\nexport interface History {\n apply(op: Op, label?: string): void;\n applyOps(ops: Op[], label: string): void;\n undo(): void;\n redo(): void;\n canUndo(): boolean;\n canRedo(): boolean;\n /** Number of entries on the undo stack (O(1); `entries().undo.length`\n * without materializing the views). */\n undoDepth(): number;\n /** Number of entries on the redo stack (O(1)). */\n redoDepth(): number;\n clear(): void;\n /** Snapshot of the current undo + redo stacks. `undo` is oldest→newest\n * (i.e. the last element is what `undo()` would pop next); `redo` is\n * also oldest→newest from the user's perspective (i.e. the *first*\n * element is what `redo()` would pop next — see implementation note).\n * Freshly built on each call, and readonly so that stays true. */\n entries(): { undo: readonly HistoryEntry[]; redo: readonly HistoryEntry[] };\n /** Walk the history forward/back until exactly `n` entries are on the\n * undo stack (0 ≤ n ≤ entries().undo.length + entries().redo.length).\n * Equivalent to repeated `undo()`/`redo()` calls but doesn't bother\n * rebuilding entry snapshots between steps. No-op if already at `n`.\n * When a step throws, as with `undo` and `redo`, the ops it ran are\n * inverted and the history stays where the call found it. */\n goto(n: number): void;\n /** The `timestamp` of entry `i`, counting in time order through the undo\n * stack and on into the redo stack, so `timestampAt(undoDepth())` is the\n * next redo's. `undefined` outside the entries. Allocates nothing. */\n timestampAt(i: number): number | undefined;\n /** The undo depth at which every entry stamped at or before `t` is applied\n * and none after it: `goto(depthAt(t))` seeks the history to `t`, either\n * way. Assumes stamps never decrease in time order, which holds while\n * `now()` does not run backward between pushes. */\n depthAt(t: number): number;\n /** Evict the oldest undo entries stamped before `t`, reporting each through\n * `onEvict` — retention by age, beside `historyLimit`'s by count. Redo\n * entries are never pruned: they are a future still reachable. */\n prune(t: number): void;\n /** The futures that fork from the current entry, oldest first: the redo\n * stack, marked `current`, and any a branching history kept. Empty when\n * there is nothing to redo and nothing was kept. */\n branches(): readonly HistoryBranch[];\n /** Make the branch `id` the redo future, keeping the one it replaces. Applies\n * nothing — both start from where the history stands. Throws when no branch\n * of that id forks from the current entry. */\n switchBranch(id: number): void;\n /** Monotonic counter bumped on every push/undo/redo/clear/coalesce.\n * Cheap to read; callers use it as a React dep to detect changes. */\n getVersion(): number;\n /** End the current coalescing run: the next entry is pushed fresh even if\n * its keys match the top entry and it lands inside the window. For a\n * caller that changed state the stack does not know about, so an entry\n * coalesced across that change would undo past it. */\n seal(): void;\n /** Subscribe to history changes. Fires after every push/undo/redo/\n * clear/coalesce. Returns an unsubscribe fn. */\n subscribe(listener: () => void): () => void;\n /** Snapshot the undo + redo stacks as `(name, args)` pairs — see\n * `SerializedHistory` for what that takes to store. An entry with an op\n * missing a `name` can't round-trip, and neither can anything that is only\n * reached by stepping across it: the snapshot keeps the undo entries newer\n * than the last such entry, the redo entries nearer than the first, and\n * the branches forking from an entry it kept. The rest is counted in\n * `droppedEntries`. The in-memory stacks aren't modified. */\n serialize(): SerializedHistory;\n /** Replace the current undo + redo stacks with the deserialized contents\n * of `snapshot`. Ops are rebuilt via the `rebuildOp` option when\n * provided, then the global registry; unknown names become no-op\n * placeholders so stack ordering survives across kit-version skew.\n * Bumps `version` and notifies subscribers exactly once. */\n restore(snapshot: SerializedHistory): void;\n /** Push an entry whose ops have already been applied to the adapter.\n * Unlike `applyOps`, does NOT call `op.apply()`. Used by Journal.commit\n * to flush a session's net forward ops to the parent as one entry without\n * re-mutating the scene. */\n recordEntry(ops: Op[], label: string, options?: RecordEntryOptions): void;\n /** Concatenated forwardOps of every undo-stack entry, in order. Snapshot\n * of \"what changes are currently applied via this history\" — useful for\n * Journal.commit to flush to a parent, and for any caller that wants to\n * diff against a baseline. */\n allForwardOps(): Op[];\n /** The id that will be assigned to the *next* pushed entry. Stable\n * monotonic counter; callers use it to tag a fork point (see Journal). */\n currentEntryId(): number;\n /** Open a scoped sub-history. All apply/undo/redo on the returned Journal\n * affect the same adapter; on commit, the Journal's net forward ops are\n * flushed to this History as one entry. See spec docs/superpowers/specs/\n * 2026-05-24-modality-design.md for the full lifecycle. */\n beginJournal(opts: BeginJournalOptions): Journal;\n /** Re-activate a suspended journal. Throws if the journal was committed or\n * cancelled (those are terminal), or if a different journal is currently\n * active — at most one journal writes to the adapter at a time, on resume\n * as well as on open. Staleness checking is the caller's\n * responsibility — consult `journal.forkedAtEntryId` against\n * `currentEntryId()` and your own op-semantic rules to decide whether\n * to resume or discard before calling this. */\n resumeJournal(journal: Journal): void;\n}\n\n/** Read/write access to whatever holds the caller's selection. The engine\n * stores the ids it is handed and hands them back on the way past; it\n * attaches no meaning to them and never records a selection change as an\n * entry of its own. */\nexport interface HistorySelection {\n get(): readonly string[];\n set(ids: readonly string[]): void;\n}\n\n/** Options for `recordEntry`. */\nexport interface RecordEntryOptions {\n /** Selection as of before the already-applied ops ran. `recordEntry` is\n * called after the fact, so the live selection has moved on by then and\n * the engine cannot sample it — a caller that wants undo to restore the\n * selection captures it when the batch opens and passes it here. */\n selectionBefore?: readonly string[];\n}\n\n/** Options for `createHistory`. */\nexport interface CreateHistoryOptions {\n /** Window (ms) within which a new entry may merge into the previous one\n * via matching `Op.coalesceKey`. Defaults to `0` (no coalescing — every\n * `applyOps` pushes a discrete entry). Recommended: ~500ms for typical\n * rapid-input UX (nudge, per-keystroke text edits). The window resets on\n * each successful coalesce, so a sustained burst keeps merging, and closes\n * on any other history operation — only the entry the last push created is\n * ever a merge target. */\n coalesceWindowMs?: number;\n /** Clock injection point for tests. Defaults to `Date.now`. */\n now?: () => number;\n /** Where the caller's selection lives. Supplied, each entry records the\n * selection around it and undo / redo / goto restore it; omitted, the\n * engine never reads or writes selection at all. */\n selection?: HistorySelection;\n /** Maximum undo-stack depth. When a push overflows the cap the oldest\n * entry is evicted (reported via `onEvict`) and can no longer be undone.\n * `0` disables the undo stack entirely — every push is evicted\n * synchronously (negative values are clamped to `0`). Default: unbounded. */\n historyLimit?: number;\n /** Fired once per entry that permanently leaves the reachable stacks:\n * redo entries dropped by a branch edit (a new push or `recordEntry`\n * after undo) and undo entries evicted by `historyLimit`.\n * NOT fired by `clear()` or `restore()` — those wholesale-replace the\n * history and the caller already knows. Note `restore()` does not enforce\n * `historyLimit` either: a restored snapshot may exceed the cap, which\n * re-applies (evicting via `onEvict`) on the next push. */\n onEvict?: (entry: EvictedEntry) => void;\n /** Keep the redo future when a push is made while there is one, as a branch\n * `branches()` lists and `switchBranch` returns to, instead of dropping it.\n * Undo and redo still walk one line, the current branch. An entry evicted by\n * `historyLimit` or `prune` takes along the branches forking from the state\n * before it, which nothing can return to; the ones forking from the state\n * it leaves stay, at the bottom of the undo stack. Default `false`. */\n branching?: boolean;\n /** Custom op rebuilder consulted by `restore()` before the global\n * op-factory registry. Return `null` to fall through (global registry,\n * then a no-op placeholder). Lets an owner rebuild ops whose handlers\n * live in per-instance state the global registry can't reach (e.g. a\n * Scene's registered op kinds).\n *\n * May be invoked more than once per entry with the same `(name, args)` —\n * once per op for `forwardOps` and again for `baseOps` (the same array\n * until a coalesce splits them). Unlike `onEvict`, a throwing hook is\n * NOT caught: it aborts `restore()` mid-rebuild and can leave the\n * stacks partially rebuilt. */\n rebuildOp?: (name: string, args: unknown) => Op | null;\n /** Diagnostics sink. Omitted, the engine is silent — it deliberately owns no\n * logging utility, so that this package depends on nothing. `@weasel-js/core`'s\n * `createHistory` wrapper routes these into its `debug/flag` namespace, which\n * is what makes `DEBUG=history` work for kit consumers. */\n debug?: HistoryLogger;\n}\n\n/** What a journal needs from a history beyond its public surface: both halves\n * of every applied entry, and a way to record both on its parent. */\ninterface JournalAccess {\n appliedOps(): { forwardOps: Op[]; baseOps: Op[] };\n record(forwardOps: Op[], baseOps: Op[], label: string, options?: RecordEntryOptions): void;\n}\n\nconst JOURNAL_ACCESS = new WeakMap<History, JournalAccess>();\n\n/** Called by `Journal.commit`. Not part of the public API. */\nexport function _journalAccessInternal(history: History): JournalAccess {\n const access = JOURNAL_ACCESS.get(history);\n if (!access) throw new Error('Not a history built by createHistory');\n return access;\n}\n\n/** Silent default — keeps every call site unconditional. */\nconst SILENT: HistoryLogger = { log: () => {}, warn: () => {} };\n\n/** Build an op-batched undo/redo `History`. The adapter is passed to each op's `apply`/`invert`. */\nexport function createHistory(adapter: unknown, options: CreateHistoryOptions = {}): History {\n const undoStack: Entry[] = [];\n const redoStack: Entry[] = [];\n let activeJournal: Journal | null = null;\n const coalesceWindowMs = options.coalesceWindowMs ?? 0;\n const now = options.now ?? (() => Date.now());\n const historyLimit = Math.max(0, options.historyLimit ?? Infinity);\n const onEvict = options.onEvict;\n const branching = options.branching ?? false;\n /** Futures a push set aside, by the id of the entry they fork from (the\n * floor's for the oldest reachable state), each in time order. Only a\n * branching history adds to it. */\n const forks = new Map<number, Entry[][]>();\n /** Names the state beneath the undo stack: 0 at the start, then the id of\n * the last undo entry evicted. */\n let floorId = 0;\n const customRebuild = options.rebuildOp;\n const selection = options.selection;\n const logger = options.debug ?? SILENT;\n let nextEntryId = 1;\n let version = 0;\n /** `version` as of the last push or coalesce. A batch may only merge into\n * the top entry while this still matches: every other operation bumps\n * `version`, so undo, redo, goto, clear, restore and `recordEntry` all\n * expire the merge window without having to remember to. Without that, an\n * edit made after stepping back rewrites whatever entry the step left on\n * top, and one undo then jumps past it. */\n let coalesceAnchorVersion = -1;\n const listeners = new Set<() => void>();\n function bump(): void {\n version++;\n for (const l of listeners) l();\n }\n\n /** Report entries that just became permanently unreachable. A throwing\n * callback must not desync the stacks mid-mutation, so failures are\n * contained and surfaced via the debug flag. */\n function reportEvicted(entries: Entry[]): void {\n if (!onEvict) return;\n for (const e of entries) {\n try {\n onEvict({ id: e.id, label: e.label, forwardOps: e.forwardOps, baseOps: e.baseOps });\n } catch (err) {\n logger.warn(`onEvict callback threw for entry id=${e.id} \"${e.label}\": ${String(err)}`);\n }\n }\n }\n\n /** Report `entries` evicted, and evict every branch forking from them. */\n function evict(entries: Entry[]): void {\n reportEvicted(entries);\n for (const e of entries) {\n const kept = forks.get(e.id);\n if (!kept) continue;\n forks.delete(e.id);\n for (const branch of kept) evict(branch);\n }\n }\n\n /** Evict `entries` off the bottom of the undo stack, oldest first. Each one\n * raises the floor to the state it leaves: the futures forking from the old\n * floor go with it, and the ones forking from the entry now fork from the\n * floor. */\n function evictFloor(entries: Entry[]): void {\n for (const e of entries) {\n reportEvicted([e]);\n const stranded = forks.get(floorId);\n forks.delete(floorId);\n floorId = e.id;\n for (const branch of stranded ?? []) evict(branch);\n }\n }\n\n /** The id of the entry the history stands on, the floor's beneath them all. */\n function forkPoint(): number {\n return undoStack[undoStack.length - 1]?.id ?? floorId;\n }\n\n /** Clear the redo stack after a push forking from `at`: kept as a branch\n * when branching, else dropped and reported. */\n function retireRedo(at: number): void {\n if (redoStack.length === 0) return;\n const future = redoStack.splice(0);\n if (!branching) return evict(future);\n const kept = forks.get(at);\n if (kept) kept.push(future.reverse());\n else forks.set(at, [future.reverse()]);\n }\n\n /** Evict the oldest undo entries past `historyLimit`, reporting each. */\n function enforceLimit(): void {\n while (undoStack.length > historyLimit) {\n evictFloor([undoStack.shift()!]);\n }\n }\n\n /** Apply `ops` best-effort while unwinding a failed step: the throw being\n * unwound is the one the caller sees, so a second one is only logged. */\n function applyUnwinding(ops: Op[]): void {\n for (const op of ops) {\n try {\n op.apply(adapter);\n } catch (err) {\n logger.warn(`unwinding a failed step: op \"${op.name ?? '?'}\" threw: ${String(err)}`);\n }\n }\n }\n\n /** Apply every op or none: when one throws, the ops that ran before it are\n * inverted, newest first, then `settle` is applied, and the throw\n * propagates. Returns true iff at least one op did NOT explicitly return\n * `false` / `'noop'` — `pushOrCoalesce` skips the entry when every op in\n * the batch was a silent no-op (e.g. reorder where the order already\n * matched). Ops returning `undefined`/`void` count as \"mutated\".\n *\n * `settle` is for a coalesced entry, whose two halves are not each\n * other's inverse: inverting what ran lands on the other half's far side,\n * and re-applying that half is what returns to where the step began. */\n function applyAllOrNothing(ops: Op[], settle?: () => Op[], idle?: Set<Op>): boolean {\n let anyMutated = false;\n let done = 0;\n try {\n for (; done < ops.length; done++) {\n const r = ops[done]!.apply(adapter);\n if (r !== false && r !== 'noop') anyMutated = true;\n else idle?.add(ops[done]!);\n }\n } catch (err) {\n if (done > 0) {\n applyUnwinding(ops.slice(0, done).reverse().map((op) => op.invert()));\n if (settle) applyUnwinding(settle());\n }\n throw err;\n }\n return anyMutated;\n }\n\n function invertEntry(entry: Entry): Op[] {\n return [...entry.baseOps].reverse().map((op) => op.invert());\n }\n\n /** Copy on read: the caller's array is theirs to mutate. */\n function readSelection(): readonly string[] | undefined {\n return selection ? [...selection.get()] : undefined;\n }\n\n /** Walk one entry backwards. The selection is restored *after* the\n * inverted ops apply, so an entry that also carries a `setSelection`-style\n * op can't overwrite the snapshot. */\n function stepBack(entry: Entry): void {\n const leaving = readSelection();\n const coalesced = entry.forwardOps !== entry.baseOps;\n applyAllOrNothing(invertEntry(entry), coalesced ? () => entry.forwardOps : undefined);\n if (leaving) entry.selectionAfter = leaving;\n if (selection && entry.selectionBefore) selection.set(entry.selectionBefore);\n }\n\n /** Walk one entry forwards. Mirror of `stepBack`. */\n function stepForward(entry: Entry): void {\n const leaving = readSelection();\n const coalesced = entry.forwardOps !== entry.baseOps;\n applyAllOrNothing(entry.forwardOps, coalesced ? () => invertEntry(entry) : undefined);\n if (leaving) entry.selectionBefore = leaving;\n if (selection && entry.selectionAfter) selection.set(entry.selectionAfter);\n }\n\n /** Step until `n` entries are on the undo stack. An entry changes stacks\n * only once its step has run, so a throw leaves both stacks describing\n * the state as it is. */\n function walkTo(n: number): void {\n while (undoStack.length > n) {\n const entry = undoStack[undoStack.length - 1]!;\n stepBack(entry);\n undoStack.pop();\n redoStack.push(entry);\n }\n while (undoStack.length < n && redoStack.length > 0) {\n const entry = redoStack[redoStack.length - 1]!;\n stepForward(entry);\n redoStack.pop();\n undoStack.push(entry);\n }\n }\n\n /** Coalesce eligibility: every op on both sides has a `coalesceKey`, and\n * the multisets of keys match (order-independent). Match by multiset\n * rather than positional index so a multi-id selection can re-emit ops in\n * any order between batches without breaking the merge. */\n function canCoalesce(top: Entry, incoming: Op[]): boolean {\n if (coalesceWindowMs <= 0) return false;\n if (version !== coalesceAnchorVersion) return false;\n if (now() - top.timestamp > coalesceWindowMs) return false;\n if (top.forwardOps.length === 0 || incoming.length === 0) return false;\n if (top.forwardOps.length !== incoming.length) return false;\n const counts = new Map<string, number>();\n for (const op of top.forwardOps) {\n const k = op.coalesceKey;\n if (k === undefined) return false;\n counts.set(k, (counts.get(k) ?? 0) + 1);\n }\n for (const op of incoming) {\n const k = op.coalesceKey;\n if (k === undefined) return false;\n const c = counts.get(k);\n if (!c) return false;\n counts.set(k, c - 1);\n }\n return true;\n }\n\n function pushOrCoalesce(applied: Op[], label: string): void {\n if (applied.length === 0) return;\n const selectionBefore = readSelection();\n const idle = new Set<Op>();\n const anyMutated = applyAllOrNothing(applied, undefined, idle);\n const ops = (idle.size === 0 ? applied : applied.filter((op) => !(op.elideNoop && idle.has(op))))\n .flatMap((op) => op.caused?.() ?? [op]);\n if (!anyMutated) {\n // Every op reported `false`/`'noop'`. Skip the push so undo stays\n // tied to real state changes. Surfaced through the kit's debug\n // flag so the upstream caller can consider avoiding the dispatch\n // entirely. Hidden by default; enable via\n // `localStorage.setItem('weasel.debug', '1')`.\n logger.warn(\n `'${label}' batch was a no-op — every op reported false/'noop'. ` +\n `Skipping the undo entry; consider gating the dispatch upstream to avoid the wasted work.`,\n );\n return;\n }\n if (ops.length === 0) return;\n const incoming = touchedIdsFromOps(ops);\n const top = undoStack[undoStack.length - 1];\n if (top && canCoalesce(top, ops)) {\n top.forwardOps = ops;\n top.timestamp = now();\n top.pushes++;\n // Merge incoming touched ids into the coalesced entry's set.\n if (incoming.size > 0) {\n const merged = new Set(top.touchedIds);\n for (const id of incoming) merged.add(id);\n top.touchedIds = merged;\n }\n // baseOps + label + id intentionally preserved — undo returns to the\n // pre-edit state, the original label sticks, and the entry id stays\n // stable so React lists keyed on id don't flicker. No dropRedo() here:\n // the anchor only survives until the stack is stepped, and every step\n // clears it, so a live anchor implies an empty redo stack.\n logger.log(`coalesce '${label}' into entry id=${top.id} (${ops.length} ops)`);\n bump();\n coalesceAnchorVersion = version;\n return;\n }\n logger.log(`push '${label}' (${ops.length} ops)`);\n const at = forkPoint();\n undoStack.push({\n id: nextEntryId++, forwardOps: ops, baseOps: ops, label, timestamp: now(), pushes: 1, touchedIds: incoming,\n ...(selectionBefore ? { selectionBefore } : {}),\n });\n retireRedo(at);\n enforceLimit();\n bump();\n coalesceAnchorVersion = version;\n }\n\n function record(forwardOps: Op[], baseOps: Op[], label: string, options: RecordEntryOptions = {}): void {\n if (forwardOps.length === 0) return;\n const at = forkPoint();\n undoStack.push({\n id: nextEntryId++, forwardOps, baseOps, label, timestamp: now(), pushes: 1,\n touchedIds: touchedIdsFromOps(forwardOps),\n ...(options.selectionBefore ? { selectionBefore: [...options.selectionBefore] } : {}),\n });\n retireRedo(at);\n enforceLimit();\n bump();\n }\n\n const history: History = {\n apply(op, label) {\n pushOrCoalesce([op], label ?? op.label ?? '');\n },\n applyOps(ops, label) {\n pushOrCoalesce(ops, label);\n },\n undo() {\n if (undoStack.length === 0) return;\n walkTo(undoStack.length - 1);\n bump();\n },\n redo() {\n if (redoStack.length === 0) return;\n walkTo(undoStack.length + 1);\n bump();\n },\n seal() {\n coalesceAnchorVersion = -1;\n },\n canUndo: () => undoStack.length > 0,\n canRedo: () => redoStack.length > 0,\n undoDepth: () => undoStack.length,\n redoDepth: () => redoStack.length,\n clear: () => {\n const had = undoStack.length > 0 || redoStack.length > 0 || forks.size > 0;\n undoStack.length = 0;\n redoStack.length = 0;\n forks.clear();\n floorId = 0;\n if (had) bump();\n },\n entries() {\n const toView = (e: Entry): HistoryEntry => ({\n id: e.id, label: e.label, timestamp: e.timestamp, pushes: e.pushes, touchedIds: e.touchedIds,\n ...(e.selectionBefore ? { selectionBefore: e.selectionBefore } : {}),\n ...(e.selectionAfter ? { selectionAfter: e.selectionAfter } : {}),\n });\n // redoStack is internally stored newest-on-top (so `pop()` redoes the\n // next-most-recent undo). Reverse on the way out so callers see the\n // entries in chronological order — the user's next redo is the first\n // element, matching `entries().redo[0]` semantics.\n return {\n undo: undoStack.map(toView),\n redo: [...redoStack].reverse().map(toView),\n };\n },\n goto(n) {\n // Total length stays constant during this walk (we only shuffle\n // entries between undo and redo stacks).\n const total = undoStack.length + redoStack.length;\n if (n < 0 || n > total || n === undoStack.length) return;\n const start = undoStack.length;\n try {\n walkTo(n);\n } catch (err) {\n // Stop where the walk began rather than partway. A second throw on\n // the way back is logged: the stacks still match the state, at\n // whatever depth that walk reached.\n try {\n walkTo(start);\n } catch (back) {\n logger.warn(`goto: returning to depth ${start} threw: ${String(back)}`);\n }\n if (undoStack.length !== start) bump();\n throw err;\n }\n bump();\n },\n timestampAt(i) {\n if (i < 0) return undefined;\n if (i < undoStack.length) return undoStack[i]!.timestamp;\n // redoStack is newest-on-top, so time order runs from its end.\n return redoStack[redoStack.length - 1 - (i - undoStack.length)]?.timestamp;\n },\n depthAt(t) {\n let n = undoStack.length;\n while (n > 0 && undoStack[n - 1]!.timestamp > t) n--;\n if (n < undoStack.length) return n;\n for (let r = redoStack.length - 1; r >= 0 && redoStack[r]!.timestamp <= t; r--) n++;\n return n;\n },\n prune(t) {\n let k = 0;\n while (k < undoStack.length && undoStack[k]!.timestamp < t) k++;\n if (k === 0) return;\n evictFloor(undoStack.splice(0, k));\n bump();\n },\n branches() {\n const view = (entries: readonly Entry[], current: boolean): HistoryBranch => {\n const first = entries[0]!;\n return { id: first.id, label: first.label, timestamp: first.timestamp, length: entries.length, current };\n };\n const out = (forks.get(forkPoint()) ?? []).map((b) => view(b, false));\n if (redoStack.length > 0) out.push(view([...redoStack].reverse(), true));\n return out.sort((a, b) => a.id - b.id);\n },\n switchBranch(id) {\n if (redoStack[redoStack.length - 1]?.id === id) return;\n const at = forkPoint();\n const kept = forks.get(at) ?? [];\n const i = kept.findIndex((b) => b[0]!.id === id);\n if (i < 0) throw new Error(`No branch ${id} forks from entry ${at}`);\n const [chosen] = kept.splice(i, 1);\n if (redoStack.length > 0) kept.push(redoStack.splice(0).reverse());\n if (kept.length > 0) forks.set(at, kept);\n else forks.delete(at);\n redoStack.push(...[...chosen!].reverse());\n bump();\n },\n getVersion: () => version,\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n serialize: () => snapshot({ undoStack, redoStack, forks, floorId, nextEntryId }, logger),\n recordEntry(ops: Op[], label: string, options?: RecordEntryOptions): void {\n record(ops, ops, label, options);\n },\n allForwardOps(): Op[] {\n const out: Op[] = [];\n for (const e of undoStack) {\n for (const op of e.forwardOps) out.push(op);\n }\n return out;\n },\n currentEntryId(): number {\n return nextEntryId;\n },\n beginJournal(opts: BeginJournalOptions): Journal {\n if (activeJournal !== null && activeJournal.isActive()) {\n throw new Error('A journal is already active — commit, cancel, or suspend it first');\n }\n // `adapter` is the closure-captured adapter passed to createHistory.\n // The returned History object's `this` doesn't carry it, so we pass\n // it through to the factory directly.\n const j = createJournalInternal(history, adapter, opts, () => { activeJournal = null; }, {\n coalesceWindowMs, now, debug: logger,\n ...(selection ? { selection } : {}),\n });\n activeJournal = j;\n return j;\n },\n resumeJournal(journal: Journal): void {\n if (activeJournal !== null && activeJournal !== journal && activeJournal.isActive()) {\n throw new Error('A journal is already active — commit, cancel, or suspend it first');\n }\n _resumeJournalInternal(journal);\n activeJournal = journal;\n },\n restore(serialized: SerializedHistory): void {\n const next = rebuilt(serialized, customRebuild, logger);\n undoStack.splice(0, undoStack.length, ...next.undoStack);\n redoStack.splice(0, redoStack.length, ...next.redoStack);\n forks.clear();\n for (const [at, kept] of next.forks) forks.set(at, kept);\n floorId = next.floorId;\n nextEntryId = next.nextEntryId;\n bump();\n },\n };\n JOURNAL_ACCESS.set(history, {\n appliedOps: () => ({\n forwardOps: undoStack.flatMap((e) => e.forwardOps),\n baseOps: undoStack.flatMap((e) => e.baseOps),\n }),\n record,\n });\n return history;\n}\n","import type { Op } from './op';\n\n/** What a {@link swapOp} carries as its `args`: the value it leaves and the one it makes. */\nexport interface SwapArgs<V> {\n before: V;\n after: V;\n}\n\n/** What {@link swapOp} is built from. */\nexport interface SwapOpOptions<V> extends SwapArgs<V> {\n /** The op's `name`, which a history's `rebuildOp` matches to rebuild it from a snapshot. */\n name: string;\n label: string;\n /** Puts a value in force. Called with `after` to apply and `before` to undo. */\n emit(value: V): void;\n coalesceKey?: string;\n}\n\n/**\n * An edit as the swap of one whole value for another. For a value held immutably the two snapshots are\n * references, and the op carries both as `args`, so a serialized history can rebuild the step.\n */\nexport function swapOp<V>({ name, label, before, after, emit, coalesceKey }: SwapOpOptions<V>): Op {\n const forward: Op = {\n label,\n name,\n args: { before, after } satisfies SwapArgs<V>,\n ...(coalesceKey !== undefined ? { coalesceKey } : {}),\n apply: () => emit(after),\n invert: () => ({ label, apply: () => emit(before), invert: () => forward }),\n };\n return forward;\n}\n"]}
|