3.8 KiB
3.8 KiB
Page Commands & Undo/Redo System Proposal
Overview
To support robust Undo/Redo functionality for the User Page Builder, we propose implementing the Command Pattern. Every modification to the page layout (add, remove, move, resize, update settings) will be encapsulated as a Command object.
Command Interface
interface Command {
id: string;
type: string;
timestamp: number;
execute(): Promise<void>;
undo(): Promise<void>;
}
Command Stack
We will maintain two stacks in the LayoutContext or a new HistoryContext:
past: Command[]future: Command[]
Proposed Commands
1. AddWidgetCommand
- Execute: Adds a widget to a specific container/index.
- Undo: Removes the widget with the specific ID.
2. RemoveWidgetCommand
- Execute: Removes a widget. Store the widget's state (props, ID, location) before removal.
- Undo: Restores the widget to its original container/index with preserved props.
3. MoveWidgetCommand
- Execute: Moves widget from (Container A, Index X) to (Container B, Index Y).
- Undo: Moves widget back to (Container A, Index X).
4. UpdateWidgetSettingsCommand
- Execute: Updates
widget.propswith new values. StorepreviousProps. - Undo: Reverts
widget.propstopreviousProps.
5. AddContainerCommand / RemoveContainerCommand
- Similar logic to widgets but for layout containers.
6. ResizeContainerCommand
- Execute: Updates container column sizes.
- Undo: Reverts to previous column sizes.
Implementation Strategy
- Refactor
useLayout: Move direct state mutations into specific command classes or factory functions. - Action Dispatcher: Create a
dispatch(action)function that:- Creates the appropriate Command.
- Executes
command.execute(). - Pushes command to
paststack. - Clears
futurestack.
- Hotkeys: Bind
Ctrl+Z(Undo) andCtrl+Y / Ctrl+Shift+Z(Redo).
Storage Boundaries & Persistence
1. In-Memory Store (Primary)
- Scope: Current Browser Tab / Session.
- Implementation: React State or
useReducerwithinLayoutContext. - Behavior: fast, synchronous updates. Cleared on page reload or navigation.
2. Browser Storage (localStorage)
- Role: Crash Recovery & Session Continuity.
- Strategy:
- Persist the
currentLayoutstate tolocalStorageon every change (debounced). - Proposed: Persist the
pastandfuturecommand stacks tolocalStorageas well. - Constraint: All
Commandobjects must be strictly JSON-serializable (no function references). - Key:
page_editor_history_${pageId}.
- Persist the
- Benefit: Users can refresh the page and still Undo their last action.
3. Server State (Database)
- Role: Permanent Storage & Collaboration Source of Truth.
- Interaction:
- "Save" commits the current state to Supabase.
- History Clearance: Typically, saving does not clear the Undo history (allowing "Undo Save"), but navigating away does.
- Dirty State: If
past.length > lastSavedIndex, the UI shows "Unsaved Changes".
4. Boundary Enforcement
- Serialization: Commands must store copies of data (e.g.,
previousProps), not references to live objects. - Isolation: Undo operations must not trigger side effects (like API calls) other than updating the local layout state, unless explicitly designed (e.g., re-uploading a deleted image is complex; usually we just restore the reference to the image URL).
Edge Cases
- Multi-user editing: Simple command history assumes single-player mode. Implementation complexity increases significantly with real-time collaboration (requiring OT or CRDTs). For now, we assume last-write-wins or locking.
- Failed operations: If
execute()fails, the command stack should not update.