🦁

Yjs における UndoManager と origin の関係

に公開

Yjs における UndoManager と origin の関係

Y.UndoManager は、特定の origin に対する変更のみを Undo/Redo の対象として記録する。
この挙動を理解しておかないと、意図した変更が Undo できない場合がある。


origin の指定と trackedOrigins

Y.Doc.transact() の第2引数には、任意の origin を指定できる。

doc.transact(() => {
  yItem.set("completed", true);
}, origin);

一方で、Y.UndoManager には trackedOrigins オプションがあり、指定された origin の変更のみが記録対象となる。

const undoManager = new Y.UndoManager(itemMap, {
  trackedOrigins: new Set([origin])
});

このとき、origin の参照が === によって一致しない場合、UndoManager はその変更を無視する。


origin が一致しないケース

以下のようなコードは UndoManager に記録されない。

doc.transact(() => {
  yItem.set("completed", true);
}, Symbol("client")); // 新規生成されるため参照が一致しない

Symbol("client") のように毎回新しく生成される値を origin に使用すると、trackedOrigins に一致する参照が存在しないため、Undo の対象とならない。


origin を省略した場合

transact() の第2引数を省略した場合、originundefined として扱われる。

doc.transact(() => {
  ...
});

この場合、UndoManager において trackedOriginsundefined が含まれていなければ、Undo 対象とはならない。Undo を有効にしたい場合は、以下のように設定する必要がある。

new Y.UndoManager(itemMap, {
  // trackedOriginsを設定しない
});

推奨パターン

明示的に origin を扱う場合は、参照が一致するように以下のように定義して使い回す。

// shared.ts
export const clientOrigin = Symbol("client");
// UndoManager
new Y.UndoManager(itemMap, {
  trackedOrigins: new Set([clientOrigin])
});

// transact
doc.transact(() => {
  ...
}, clientOrigin);

備考

  • origin は複数の UndoManager による変更区別や、ローカル/リモート操作の識別に用いることができる
  • originSymbol を用いることで、衝突のない一意な識別が可能となる

Discussion