Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions document/format/src/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,23 @@ impl<L: Layout> Gdd<L> {
Ok(())
}

/// Move the head back to an ancestor, keeping the later line as a branch. A non-retiring peer asks the retirer.
#[cfg(feature = "network")]
pub fn move_head(&mut self, rev: Rev) -> Result<(), Error> {
if self.retires_locally() {
let (moved, touched) = self.session.move_head_to(rev)?;
self.remote_changes.touched.extend(touched);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This records only the touched entities, but head movement can change registry fields that Touched does not track. Mark the change as requiring a full rebuild, otherwise the History panel can move the storage cursor while the editor still displays the old state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At document/format/src/persist.rs, line 461:

<comment>This records only the touched entities, but head movement can change registry fields that `Touched` does not track. Mark the change as requiring a full rebuild, otherwise the History panel can move the storage cursor while the editor still displays the old state.</comment>

<file context>
@@ -452,6 +452,24 @@ impl<L: Layout> Gdd<L> {
+	pub fn move_head(&mut self, rev: Rev) -> Result<(), Error> {
+		if self.retires_locally() {
+			let (moved, touched) = self.session.move_head_to(rev)?;
+			self.remote_changes.touched.extend(touched);
+			self.persist_registry_snapshot()?;
+			self.persist_session_state()?;
</file context>
Suggested change
self.remote_changes.touched.extend(touched);
self.remote_changes.touched.extend(touched);
self.remote_changes.rebuilt = true;

self.persist_registry_snapshot()?;
self.persist_session_state()?;
if let Some(replica) = &mut self.network {
replica.broadcast_head_move(moved)?;
}
} else if let Some(replica) = &mut self.network {
replica.request_move(rev)?;
}
Ok(())
}

/// Redo a dropped step as a copy on top of the line. A non-retiring peer asks the retirer.
#[cfg(feature = "network")]
pub fn redo_retired_step(&mut self, rev: Rev) -> Result<(), Error> {
Expand Down
1 change: 1 addition & 0 deletions editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ impl Dispatcher {
let layout = &self.message_handlers.portfolio_message_handler.workspace.panel_layout;
menu_bar_message_handler.data_panel_open = layout.is_panel_present(PanelType::Data);
menu_bar_message_handler.session_panel_open = layout.is_panel_present(PanelType::Session);
menu_bar_message_handler.history_panel_open = layout.is_panel_present(PanelType::History);
menu_bar_message_handler.layers_panel_open = layout.is_panel_present(PanelType::Layers);
menu_bar_message_handler.inspector_open = layout.is_panel_present(PanelType::Inspector);
menu_bar_message_handler.message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
Expand Down
3 changes: 3 additions & 0 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ pub enum FrontendMessage {
UpdateDocumentCursors {
cursors: Vec<FrontendRemoteCursor>,
},
UpdateHistoryPanel {
state: crate::messages::portfolio::history::utility_types::HistoryPanelState,
},
UpdateNodeGraphCursors {
cursors: Vec<FrontendRemoteCursor>,
},
Expand Down
6 changes: 6 additions & 0 deletions editor/src/messages/menu_bar/menu_bar_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct MenuBarMessageHandler {
pub make_path_editable_is_allowed: bool,
pub data_panel_open: bool,
pub session_panel_open: bool,
pub history_panel_open: bool,
pub layers_panel_open: bool,
pub inspector_open: bool,
pub focus_document: bool,
Expand Down Expand Up @@ -693,6 +694,11 @@ impl LayoutHolder for MenuBarMessageHandler {
.icon(if self.session_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" })
.on_commit(|_| WorkspaceMessage::ToggleSessionPanelOpen.into())
.disabled(self.focus_document),
MenuListEntry::new("History")
.label("History")
.icon(if self.history_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" })
.on_commit(|_| WorkspaceMessage::ToggleHistoryPanelOpen.into())
.disabled(self.focus_document),
],
])
.widget_instance(),
Expand Down
33 changes: 27 additions & 6 deletions editor/src/messages/portfolio/document/document_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ impl DocumentHistory {
}
match note {
UndoNote::Retracted(ops) => self.restage_ops(ops),
UndoNote::Dropped(rev) => self.redo_retired_step(rev),
UndoNote::Dropped(rev) => {
self.redo_retired_step(rev);
}
UndoNote::Cursor => {}
}
true
Expand Down Expand Up @@ -219,11 +221,30 @@ impl DocumentHistory {
self.storage.as_ref().map(|storage| storage.resource_gc_roots()).unwrap_or_default()
}

pub fn redo_retired_step(&mut self, rev: document_graph_storage::Rev) {
let Some(storage) = self.storage.as_mut() else { return };
if let Err(error) = storage.redo_retired_step(rev) {
log::error!("Redoing the dropped step failed: {error}");
}
pub fn redo_retired_step(&mut self, rev: document_graph_storage::Rev) -> bool {
self.change_line(|storage| storage.redo_retired_step(rev), "Redoing the dropped step failed")
}

pub fn drop_step(&mut self, rev: document_graph_storage::Rev) -> bool {
self.change_line(|storage| storage.drop_step(rev), "Removing the step failed")
}

pub fn move_head_to(&mut self, rev: document_graph_storage::Rev) -> bool {
self.change_line(|storage| storage.move_head(rev), "Moving the head failed")
}

fn change_line(&mut self, change: impl FnOnce(&mut document_format::GddV1) -> Result<(), document_format::Error>, failure: &str) -> bool {
let Some(storage) = self.storage.as_mut().filter(|storage| storage.can_change_line()) else {
return false;
};
change(storage).inspect_err(|error| log::error!("{failure}: {error}")).is_ok()
}

/// Forgets the legacy snapshots once the head has moved off their line.
pub fn clear_legacy(&mut self) {
self.legacy_undo_stack.clear();
self.legacy_redo_stack.clear();
self.undo_notes.clear();
}

pub fn retire_storage_interaction(&mut self) {
Expand Down
11 changes: 11 additions & 0 deletions editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ pub enum DocumentMessage {
DeselectAllLayers,
DocumentHistoryBackward,
DocumentHistoryForward,
/// Moves the shared head to an earlier interaction and keeps the later steps as a branch.
HistoryMoveHead {
rev: document_graph_storage::Rev,
},
/// Drops a retired interaction and mints the later ones again on its parent.
HistoryRemoveStep {
rev: document_graph_storage::Rev,
},
HistoryRestoreStep {
rev: document_graph_storage::Rev,
},
DocumentStructureChanged,
DrawArtboardOverlays {
context: OverlayContext,
Expand Down
47 changes: 47 additions & 0 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,27 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
self.undo_with_history(document_id, viewport, preferences.validate_storage_round_trip, responses)
}
DocumentMessage::DocumentHistoryForward => self.redo_with_history(document_id, viewport, preferences.validate_storage_round_trip, responses),
DocumentMessage::HistoryMoveHead { rev } => self.apply_history_action(
document_id,
|history| history.move_head_to(rev),
&resource_storage.resources_mut(),
preferences.validate_storage_round_trip,
responses,
),
DocumentMessage::HistoryRemoveStep { rev } => self.apply_history_action(
document_id,
|history| history.drop_step(rev),
&resource_storage.resources_mut(),
preferences.validate_storage_round_trip,
responses,
),
DocumentMessage::HistoryRestoreStep { rev } => self.apply_history_action(
document_id,
|history| history.redo_retired_step(rev),
&resource_storage.resources_mut(),
preferences.validate_storage_round_trip,
responses,
),
DocumentMessage::DocumentStructureChanged => {
if layers_panel_open {
self.network_interface.load_structure();
Expand Down Expand Up @@ -2997,6 +3018,32 @@ impl DocumentMessageHandler {
self.drive_storage_undo_redo(document_id, legacy_applied, true, validate, responses);
}

/// Performs a History panel action in storage and drops the legacy snapshots, which are off the head's line.
fn apply_history_action(
&mut self,
document_id: DocumentId,
action: impl FnOnce(&mut DocumentHistory) -> bool,
byte_store: &dyn ResourceStorage,
validate: bool,
responses: &mut VecDeque<Message>,
) {
if self.network_interface.transaction_status() != TransactionStatus::Finished {
return;
}
responses.add(EventMessage::ToolAbort);
self.stage_edits_outside_transaction(byte_store, validate);
self.history.retire_storage_interaction();
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
let stored_name = self.stored_name().map(str::to_string);
if !action(&mut self.history) {
return;
}
self.history.clear_legacy();
self.follow_storage_changes(stored_name, responses);
responses.add(PortfolioMessage::AutoSaveDocument { document_id });
responses.add(OverlaysMessage::Draw);
responses.add(EventMessage::SelectionChanged);
}

/// Reconciles the interface with storage. The tab takes the stored name only if it changed from `name_before`.
fn follow_storage_changes(&mut self, name_before: Option<String>, responses: &mut VecDeque<Message>) {
if let Some(gdd) = self.history.storage_mut() {
Expand Down
16 changes: 16 additions & 0 deletions editor/src/messages/portfolio/history/history_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::messages::prelude::*;

#[impl_message(Message, PortfolioMessage, History)]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum HistoryMessage {
Refresh,
Tick,
Expand { id: String, expanded: bool },
LoadMore,
GoBack { id: String },
RemoveStep { id: String },
BringBack { id: String },
Follow { id: Option<String> },
Rename { id: String, label: String },
Tag { id: String, tag: String, on: bool },
}
Loading
Loading