From harness-claude
Implements GoF Memento pattern in TypeScript for undo/redo functionality and state snapshots, preserving encapsulation. Useful for editors, games, or any system requiring time-travel state restoration.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:gof-memento-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Capture and restore object state using mementos for undo history and time-travel.
Capture and restore object state using mementos for undo history and time-travel.
Classic memento with Originator and Caretaker:
// Memento — stores a snapshot of state
class EditorMemento {
constructor(
private readonly content: string,
private readonly cursorPosition: number,
private readonly timestamp: Date
) {}
getContent(): string {
return this.content;
}
getCursorPosition(): number {
return this.cursorPosition;
}
getTimestamp(): Date {
return this.timestamp;
}
describe(): string {
return `[${this.timestamp.toISOString()}] ${this.content.slice(0, 30)}...`;
}
}
// Originator — creates and restores from mementos
class TextEditor {
private content = '';
private cursorPosition = 0;
type(text: string): void {
this.content =
this.content.slice(0, this.cursorPosition) + text + this.content.slice(this.cursorPosition);
this.cursorPosition += text.length;
}
moveCursor(position: number): void {
this.cursorPosition = Math.max(0, Math.min(position, this.content.length));
}
delete(count: number): void {
this.content =
this.content.slice(0, this.cursorPosition - count) + this.content.slice(this.cursorPosition);
this.cursorPosition = Math.max(0, this.cursorPosition - count);
}
// Save state to memento
save(): EditorMemento {
return new EditorMemento(this.content, this.cursorPosition, new Date());
}
// Restore state from memento
restore(memento: EditorMemento): void {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
getState(): { content: string; cursor: number } {
return { content: this.content, cursor: this.cursorPosition };
}
}
// Caretaker — manages the history of mementos
class EditorHistory {
private history: EditorMemento[] = [];
private future: EditorMemento[] = [];
save(editor: TextEditor): void {
this.history.push(editor.save());
this.future = []; // clear redo history
}
undo(editor: TextEditor): boolean {
if (this.history.length === 0) return false;
this.future.push(editor.save());
editor.restore(this.history.pop()!);
return true;
}
redo(editor: TextEditor): boolean {
if (this.future.length === 0) return false;
this.history.push(editor.save());
editor.restore(this.future.pop()!);
return true;
}
getHistoryDescriptions(): string[] {
return this.history.map((m) => m.describe());
}
}
// Usage
const editor = new TextEditor();
const history = new EditorHistory();
history.save(editor);
editor.type('Hello, world!');
history.save(editor);
editor.type(' How are you?');
history.save(editor);
editor.delete(4);
console.log(editor.getState()); // { content: 'Hello, world! How are', cursor: 21 }
history.undo(editor);
console.log(editor.getState()); // { content: 'Hello, world! How are you?', cursor: 26 }
history.undo(editor);
console.log(editor.getState()); // { content: 'Hello, world!', cursor: 13 }
history.redo(editor);
console.log(editor.getState()); // { content: 'Hello, world! How are you?', cursor: 26 }
Lightweight memento using plain objects (TypeScript idiomatic):
type FormState = {
firstName: string;
lastName: string;
email: string;
step: number;
};
class MultiStepForm {
private state: FormState = { firstName: '', lastName: '', email: '', step: 1 };
private snapshots: FormState[] = [];
updateField<K extends keyof FormState>(field: K, value: FormState[K]): void {
this.state = { ...this.state, [field]: value };
}
checkpoint(): void {
this.snapshots.push({ ...this.state }); // shallow copy sufficient for flat state
}
rollback(): boolean {
const snapshot = this.snapshots.pop();
if (!snapshot) return false;
this.state = snapshot;
return true;
}
getState(): Readonly<FormState> {
return this.state;
}
}
Encapsulation is key: The Originator creates and restores mementos. The Caretaker stores them but must NOT access their internal data. In TypeScript, enforce this with private constructors or closures.
Memory management: Unlimited undo history can exhaust memory. Implement a fixed-size ring buffer or time-limited history:
class BoundedHistory {
private history: Memento[] = [];
constructor(private readonly maxSize: number) {}
push(memento: Memento): void {
this.history.push(memento);
if (this.history.length > this.maxSize) {
this.history.shift(); // drop oldest
}
}
}
Anti-patterns:
Memento vs. Command: Command stores the operation needed to undo an action. Memento stores a complete state snapshot. Command is more memory-efficient for simple state changes; Memento is simpler to implement when state is complex and hard to invert.
refactoring.guru/design-patterns/memento
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeCaptures and restores object state for undo/redo, rollback, or snapshot functionality without breaking encapsulation.
Generates Memento design pattern in PHP 8.4 for undo/redo state capture and restoration, with originator, memento, caretaker, value objects, and unit tests.
Encapsulates operations as command objects with undo/redo support and command queuing. Provides a Command interface and CommandHistory class for building action history systems, task queues, or job schedulers.