From harness-claude
Captures and restores object states using Memento pattern for undo/redo history and time-travel debugging in text editors, drawing apps, forms, or games.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Capture and restore object state using mementos for undo history and time-travel.
Generates Memento design pattern in PHP 8.4 for undo/redo state capture and restoration, with originator, memento, caretaker, value objects, and unit tests.
Implements GoF Command pattern in TypeScript: encapsulate operations as objects for undo/redo, queuing, transaction logs, and parameterizing methods. Use for task queues or action histories.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
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