Skip to content
Draft
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
19 changes: 7 additions & 12 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,6 @@ pub struct DocumentMessageHandler {
/// Tracks which layer occurrences are collapsed in the Layers panel, keyed by tree path.
#[serde(deserialize_with = "deserialize_collapsed_layers", default)]
pub collapsed: CollapsedLayers,
/// The node IDs whose section is collapsed in the Properties panel.
#[serde(default)]
pub properties_panel_collapsed_sections: Vec<NodeId>,
/// The full Git commit hash of the Graphite repository that was used to build the editor.
/// We save this to provide a hint about which version of the editor was used to create the document.
pub commit_hash: String,
Expand Down Expand Up @@ -178,7 +175,6 @@ impl Default for DocumentMessageHandler {
network_interface: default_document_network_interface(),
resources: ResourceMessageHandler::default(),
collapsed: CollapsedLayers::default(),
properties_panel_collapsed_sections: Vec::new(),
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
document_ptz: PTZ::default(),
render_mode: RenderMode::default(),
Expand Down Expand Up @@ -253,7 +249,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
document_name: self.name.as_str(),
fonts,
properties_panel_open,
properties_panel_collapsed_sections: &self.properties_panel_collapsed_sections,
properties_panel_collapsed_sections: &[],

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: Opening a document saved by the previous editor drops its Properties-panel collapse preferences. Retaining a legacy serde field and migrating its node IDs into persistent_metadata.collapsed during deserialization would preserve existing documents.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/document_message_handler.rs, line 252:

<comment>Opening a document saved by the previous editor drops its Properties-panel collapse preferences. Retaining a legacy serde field and migrating its node IDs into `persistent_metadata.collapsed` during deserialization would preserve existing documents.</comment>

<file context>
@@ -253,7 +249,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
 					fonts,
 					properties_panel_open,
-					properties_panel_collapsed_sections: &self.properties_panel_collapsed_sections,
+					properties_panel_collapsed_sections: &[],
 				};
 				self.properties_panel_message_handler.process_message(message, responses, context);
</file context>

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.

P3: The properties_panel_collapsed_sections field on NodePropertiesContext and PropertiesPanelMessageContext is now dead code. The collapsed state is read from network_interface.is_collapsed() instead (node_properties.rs:2464). The field is always passed as &[] and never consumed. Remove it from both context structs and all construction sites to avoid confusion.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/document_message_handler.rs, line 252:

<comment>The `properties_panel_collapsed_sections` field on `NodePropertiesContext` and `PropertiesPanelMessageContext` is now dead code. The collapsed state is read from `network_interface.is_collapsed()` instead (`node_properties.rs:2464`). The field is always passed as `&[]` and never consumed. Remove it from both context structs and all construction sites to avoid confusion.</comment>

<file context>
@@ -253,7 +249,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
 					fonts,
 					properties_panel_open,
-					properties_panel_collapsed_sections: &self.properties_panel_collapsed_sections,
+					properties_panel_collapsed_sections: &[],
 				};
 				self.properties_panel_message_handler.process_message(message, responses, context);
</file context>

};
self.properties_panel_message_handler.process_message(message, responses, context);
}
Expand All @@ -277,7 +273,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
breadcrumb_network_path: &self.breadcrumb_network_path,
document_id,
collapsed: &mut self.collapsed,
properties_panel_collapsed_sections: &mut self.properties_panel_collapsed_sections,
ipp,
graph_view_overlay_open: self.graph_view_overlay_open,
graph_fade_artwork_percentage: self.graph_fade_artwork_percentage,
Expand Down Expand Up @@ -1411,12 +1406,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(NodeGraphMessage::SendGraph);
}
DocumentMessage::ToggleNodePropertiesSectionExpanded { node_id } => {
if let Some(index) = self.properties_panel_collapsed_sections.iter().position(|id| *id == node_id) {
self.properties_panel_collapsed_sections.remove(index);
} else {
self.properties_panel_collapsed_sections.push(node_id);
}
responses.add(PropertiesPanelMessage::Refresh);
let collapsed = !self.network_interface.is_collapsed(&node_id, &[]);

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: ToggleNodePropertiesSectionExpanded checks the collapsed state in the document network (&[]), but the subsequent SetCollapsed message will be processed with selection_network_path from the NodeGraphMessageHandler context, which may point to a nested network. If the user is working inside a nested network (e.g. a group layer), is_collapsed(&node_id, &[]) won't find the node there and returns false (default), so the toggle always computes collapsed = true — the section stays collapsed regardless of the previous state, breaking the toggle for nested-network nodes. The read and write must use the same network path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/document_message_handler.rs, line 1409:

<comment>`ToggleNodePropertiesSectionExpanded` checks the collapsed state in the document network (`&[]`), but the subsequent `SetCollapsed` message will be processed with `selection_network_path` from the `NodeGraphMessageHandler` context, which may point to a nested network. If the user is working inside a nested network (e.g. a group layer), `is_collapsed(&node_id, &[])` won't find the node there and returns `false` (default), so the toggle always computes `collapsed = true` — the section stays collapsed regardless of the previous state, breaking the toggle for nested-network nodes. The read and write must use the same network path.</comment>

<file context>
@@ -1411,12 +1406,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
-					self.properties_panel_collapsed_sections.push(node_id);
-				}
-				responses.add(PropertiesPanelMessage::Refresh);
+				let collapsed = !self.network_interface.is_collapsed(&node_id, &[]);
+				responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed });
+				responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
</file context>

responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed });
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
node_ids: vec![node_id],
network_path: vec![],
});
}
DocumentMessage::ToggleSelectedLocked => responses.add(NodeGraphMessage::ToggleSelectedLocked),
DocumentMessage::ToggleSelectedVisibility => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
let new_merge_node = resolve_network_node_type("Merge").expect("Merge node").default_node_template();
self.network_interface.insert_node(new_id, new_merge_node, &[]);
self.responses.add(PropertiesPanelMessage::SetSectionExpanded { node_id: new_id.0, expanded: false });

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: During bulk SVG import this queues a large flood of PropertiesPanelMessage cascades (SetCollapsed + SetLockedOrVisibilitySideEffects → RunDocumentGraph/SendGraph/UpdateLayerPanel/AutoSave/Refresh) for every created layer, since create_layer is invoked once per node. The collapsed section state is only needed for layers the user creates interactively, so guard the emission with self.import to skip it on bulk import.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/graph_operation/utility_types.rs, line 65:

<comment>During bulk SVG import this queues a large flood of PropertiesPanelMessage cascades (SetCollapsed + SetLockedOrVisibilitySideEffects → RunDocumentGraph/SendGraph/UpdateLayerPanel/AutoSave/Refresh) for every created layer, since create_layer is invoked once per node. The collapsed section state is only needed for layers the user creates interactively, so guard the emission with `self.import` to skip it on bulk import.</comment>

<file context>
@@ -62,6 +62,7 @@ impl<'a> ModifyInputsContext<'a> {
 	pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
 		let new_merge_node = resolve_network_node_type("Merge").expect("Merge node").default_node_template();
 		self.network_interface.insert_node(new_id, new_merge_node, &[]);
+		self.responses.add(PropertiesPanelMessage::SetSectionExpanded { node_id: new_id.0, expanded: false });
 		LayerNodeIdentifier::new(new_id, self.network_interface)
 	}
</file context>

LayerNodeIdentifier::new(new_id, self.network_interface)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use graphene_std::vector::Vector;
use graphene_std::*;
use std::collections::{HashMap, VecDeque};

pub const MERGE_NODE_IDENTIFIER: &str = "Merge";

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.

P3: The new pub const MERGE_NODE_IDENTIFIER is used in only one place, while the identical literal "Merge" remains hardcoded in sibling comparisons (e.g. document_node_derive.rs, view.rs is_merge/is_collapsed, document_migration.rs, graph_modification_utils.rs). Since the constant names the shared Merge network identifier, using it consistently avoids future drift if the display/identifier value ever changes. Consider referencing MERGE_NODE_IDENTIFIER in those checks as well, or dropping the constant if only this one site is intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs, line 26:

<comment>The new pub const MERGE_NODE_IDENTIFIER is used in only one place, while the identical literal "Merge" remains hardcoded in sibling comparisons (e.g. document_node_derive.rs, view.rs is_merge/is_collapsed, document_migration.rs, graph_modification_utils.rs). Since the constant names the shared Merge network identifier, using it consistently avoids future drift if the display/identifier value ever changes. Consider referencing MERGE_NODE_IDENTIFIER in those checks as well, or dropping the constant if only this one site is intended.</comment>

<file context>
@@ -23,6 +23,8 @@ use graphene_std::vector::Vector;
 use graphene_std::*;
 use std::collections::{HashMap, VecDeque};
 
+pub const MERGE_NODE_IDENTIFIER: &str = "Merge";
+
 pub struct NodePropertiesContext<'a> {
</file context>


pub struct NodePropertiesContext<'a> {
pub responses: &'a mut VecDeque<Message>,
pub executor: &'a mut NodeGraphExecutor,
Expand Down Expand Up @@ -145,7 +147,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
properties: None,
},
DocumentNodeDefinition {
identifier: "Merge",
identifier: MERGE_NODE_IDENTIFIER,
category: "General",
node_template: NodeTemplate {
implementation: NodeTemplateImplementation::Network(NodeNetworkTemplate {
Expand Down Expand Up @@ -1543,7 +1545,6 @@ impl DocumentNodeDefinition {
template
}

/// Converts the [DocumentNodeDefinition] type to a [NodeTemplate], completely default.
pub fn default_node_template(&self) -> NodeTemplate {
self.node_template_input_override(self.node_template.inputs.clone().into_iter().map(Some))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ pub enum NodeGraphMessage {
node_id: NodeId,
pinned: bool,
},
SetCollapsed {
node_id: NodeId,
collapsed: bool,
},
SetVisibility {
node_id: NodeId,
network_path: Vec<NodeId>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ pub struct NodeGraphMessageContext<'a> {
pub breadcrumb_network_path: &'a [NodeId],
pub document_id: DocumentId,
pub collapsed: &'a mut CollapsedLayers,
pub properties_panel_collapsed_sections: &'a mut Vec<NodeId>,
pub ipp: &'a InputPreprocessorMessageHandler,
pub graph_view_overlay_open: bool,
pub graph_fade_artwork_percentage: f64,
Expand Down Expand Up @@ -111,7 +110,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
breadcrumb_network_path,
document_id,
collapsed,
properties_panel_collapsed_sections,
ipp,
graph_view_overlay_open,
graph_fade_artwork_percentage,
Expand Down Expand Up @@ -193,10 +191,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG

// Prune the Layers panel collapsed state for any layer tree paths whose nodes no longer exist, so it doesn't accumulate across loads
collapsed.0.retain(|path| path.iter().all(|&node_id| network_interface.document_network().nodes.contains_key(&node_id)));

// Prune the Properties panel node section collapsed state for any nodes (in any nested network) that no longer exist, so it doesn't accumulate across loads
let existing_nodes = network_interface.document_network().recursive_nodes().map(|(node_id, ..)| *node_id).collect::<HashSet<_>>();
properties_panel_collapsed_sections.retain(|node_id| existing_nodes.contains(node_id));
}
NodeGraphMessage::SelectedNodesUpdated => {
let selected_layers = network_interface.selected_nodes().selected_layers(network_interface.document_metadata()).collect::<Vec<_>>();
Expand Down Expand Up @@ -2029,6 +2023,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::SetPinned { node_id, pinned } => {
network_interface.set_pinned(&node_id, selection_network_path, pinned);
}
NodeGraphMessage::SetCollapsed { node_id, collapsed } => {
network_interface.set_collapsed(&node_id, selection_network_path, collapsed);

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: Root Properties sections can fail to expand or collapse when the current selection is inside a nested network: SetCollapsed targets selection_network_path, even though the document-level toggle explicitly targets the root network. Carry the node's containing network path in SetCollapsed (and pass it from each caller) so the mutation and the read use the same target.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs, line 2021:

<comment>Root Properties sections can fail to expand or collapse when the current selection is inside a nested network: `SetCollapsed` targets `selection_network_path`, even though the document-level toggle explicitly targets the root network. Carry the node's containing network path in `SetCollapsed` (and pass it from each caller) so the mutation and the read use the same target.</comment>

<file context>
@@ -2023,6 +2017,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
 				network_interface.set_pinned(&node_id, selection_network_path, pinned);
 			}
+			NodeGraphMessage::SetCollapsed { node_id, collapsed } => {
+				network_interface.set_collapsed(&node_id, selection_network_path, collapsed);
+			}
 			NodeGraphMessage::SetVisibility { node_id, network_path, visible } => {
</file context>

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: Expanding or collapsing a Properties section is not undoable because this handler mutates the persistent node metadata without ensuring a transaction has started. Start a document transaction before dispatching SetCollapsed from each direct caller, or provide a transaction-aware collapse command.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs, line 2027:

<comment>Expanding or collapsing a Properties section is not undoable because this handler mutates the persistent node metadata without ensuring a transaction has started. Start a document transaction before dispatching `SetCollapsed` from each direct caller, or provide a transaction-aware collapse command.</comment>

<file context>
@@ -2029,6 +2023,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
 				network_interface.set_pinned(&node_id, selection_network_path, pinned);
 			}
+			NodeGraphMessage::SetCollapsed { node_id, collapsed } => {
+				network_interface.set_collapsed(&node_id, selection_network_path, collapsed);
+			}
 			NodeGraphMessage::SetVisibility { node_id, network_path, visible } => {
</file context>

}
NodeGraphMessage::SetVisibility { node_id, network_path, visible } => {
network_interface.set_visibility(&node_id, &network_path, visible);
}
Expand All @@ -2038,6 +2035,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
responses.add(NodeGraphMessage::UpdateActionButtons);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::UpdateLayerPanel);
responses.add(PortfolioMessage::AutoSaveActiveDocument);

responses.add(PropertiesPanelMessage::Refresh);
responses.add(DataPanelMessage::Refresh);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2347,7 +2347,6 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
if layout.is_empty() {
layout = node_no_properties(node_id, context);
}

let display_name = context
.network_interface
.node_metadata(&node_id, context.selection_network_path)
Expand All @@ -2374,7 +2373,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper

let visible = context.network_interface.is_visible(&node_id, context.selection_network_path);
let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path);
let expanded = !context.properties_panel_collapsed_sections.contains(&node_id);
let expanded = !context.network_interface.is_collapsed(&node_id, context.selection_network_path);

LayoutGroup::section(name, description, visible, pinned, expanded, node_id.0, Layout(layout))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ pub enum PropertiesPanelMessage {
// Messages
Clear,
Refresh,
SetAllSectionsExpanded { expanded: bool },
SetSectionExpanded { node_id: u64, expanded: bool },
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,62 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
layout_target: LayoutTarget::PropertiesPanel,
});
}
PropertiesPanelMessage::SetAllSectionsExpanded { expanded } => {
let mut layout = {
let mut node_properties_context = NodePropertiesContext {
responses,
executor,
network_interface,
resources,
fonts,
selection_network_path,
document_name,
properties_panel_collapsed_sections,
};
Layout(NodeGraphMessageHandler::collate_properties(&mut node_properties_context))
};

let node_ids = Self::update_all_section_expansion_recursive(&mut layout.0, expanded, responses);
if !node_ids.is_empty() {
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids, network_path: vec![] });
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}

responses.add(LayoutMessage::SendLayout {
layout,
layout_target: LayoutTarget::PropertiesPanel,
});
}
PropertiesPanelMessage::SetSectionExpanded { node_id, expanded } => {
let node_id = NodeId(node_id);
responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed: !expanded });
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
node_ids: vec![node_id],
network_path: vec![],
});
}
}
}

fn actions(&self) -> ActionList {
actions!(PropertiesMessageDiscriminant;)
}
}

impl PropertiesPanelMessageHandler {
fn update_all_section_expansion_recursive(layout: &mut [LayoutGroup], expanded: bool, responses: &mut VecDeque<Message>) -> Vec<NodeId> {
let mut node_ids = Vec::new();
for group in layout {
if let LayoutGroup::Section(WidgetSection {
id, layout, expanded: group_expanded, ..
}) = group
{
*group_expanded = expanded;
let node_id = NodeId(*id);
node_ids.push(node_id);
responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed: !expanded });
node_ids.extend(Self::update_all_section_expansion_recursive(&mut layout.0, expanded, responses));
}
}
node_ids
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ impl From<DocumentNodePersistentMetadataStringReference> for DocumentNodePersist
output_names: old.output_names,
locked: old.locked,
pinned: old.pinned,
collapsed: None,
node_type_metadata: old.node_type_metadata,
network_metadata: old.network_metadata,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,16 @@ impl NodeNetworkInterface {
self.transaction_modified();
}

pub fn set_collapsed(&mut self, node_id: &NodeId, network_path: &[NodeId], collapsed: bool) {
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
log::error!("Could not get node {node_id} in set_collapsed");
return;
};

node_metadata.persistent_metadata.collapsed = Some(collapsed);
self.transaction_modified();
}

/// Reorders a pinned node within its network's Properties panel display order so it ends up at `insert_index` among the
/// pinned nodes (0 being the topmost). Rebuilds the order from the list as currently shown, which also drops stale entries.
pub fn reorder_pinned_node(&mut self, node_id: NodeId, insert_index: usize, network_path: &[NodeId]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,10 @@ impl NodeNetworkInterface {
self.query(network_path, "is_pinned", |view| view.is_pinned(node_id)).unwrap_or_default()
}

pub fn is_collapsed(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
self.query(network_path, "is_collapsed", |view| view.is_collapsed(node_id)).unwrap_or_default()
}

/// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last.
pub fn ordered_pinned_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
self.query(network_path, "ordered_pinned_nodes", |view| Ok(view.ordered_pinned_nodes())).unwrap_or_default()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ impl NodeTemplate {
output_names,
locked,
pinned,
collapsed: _,
node_type_metadata,
network_metadata,
} = persistent_node_metadata;
Expand Down Expand Up @@ -201,6 +202,7 @@ impl NodeTemplate {
output_names,
locked,
pinned,
collapsed: None,

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.

P1: The new collapsed state is silently lost whenever a node passes through the NodeTemplate split/join cycle, defeating the persistence this PR is adding. NodeTemplate::from_parts drops the field on join (collapsed: _) and into_parts unconditionally writes collapsed: None on split. NodeTemplate::normalize_stored_types round-trips every nested node through these two methods and is called on every document open (document_migration_upgrades -> normalize_stored_types at document_migration.rs:1134), so any collapse preference the user saves is reset to None on the next load. Since collapsed is part of DocumentNodePersistentMetadata (serde-persisted), it should be carried through the template shape like pinned/locked rather than dropped.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/utility_types/network_interface/template.rs, line 193:

<comment>The new `collapsed` state is silently lost whenever a node passes through the `NodeTemplate` split/join cycle, defeating the persistence this PR is adding. `NodeTemplate::from_parts` drops the field on join (`collapsed: _`) and `into_parts` unconditionally writes `collapsed: None` on split. `NodeTemplate::normalize_stored_types` round-trips every nested node through these two methods and is called on every document open (`document_migration_upgrades` -> `normalize_stored_types` at document_migration.rs:1134), so any collapse preference the user saves is reset to `None` on the next load. Since `collapsed` is part of `DocumentNodePersistentMetadata` (serde-persisted), it should be carried through the template shape like `pinned`/`locked` rather than dropped.</comment>

<file context>
@@ -189,6 +190,7 @@ impl NodeTemplate {
 			output_names,
 			locked,
 			pinned,
+			collapsed: None,
 			node_type_metadata,
 			network_metadata,
</file context>

node_type_metadata,
network_metadata,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,8 @@ pub struct DocumentNodePersistentMetadata {
/// Indicates that the node will be shown in the Properties panel when it would otherwise be empty, letting a user easily edit its properties by just deselecting everything.
#[serde(default)]
pub pinned: bool,
#[serde(default)]
pub collapsed: Option<bool>,

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.

P1: Explicit Properties-panel collapse choices are lost on persistence, so reopening a document can restore a section to the implementation default instead of the user’s state. Carry collapsed through the GDD metadata and template conversion paths, or intentionally persist it in the existing session view metadata.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/utility_types/network_interface/types.rs, line 603:

<comment>Explicit Properties-panel collapse choices are lost on persistence, so reopening a document can restore a section to the implementation default instead of the user’s state. Carry `collapsed` through the GDD metadata and template conversion paths, or intentionally persist it in the existing session view metadata.</comment>

<file context>
@@ -599,6 +599,8 @@ pub struct DocumentNodePersistentMetadata {
 	#[serde(default)]
 	pub pinned: bool,
+	#[serde(default)]
+	pub collapsed: Option<bool>,
 	/// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.
 	/// All fields in NodeTypePersistentMetadata should automatically be updated by using the network interface API
</file context>

/// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.
/// All fields in NodeTypePersistentMetadata should automatically be updated by using the network interface API
pub node_type_metadata: NodeTypePersistentMetadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ impl<'a, 'p> NetworkView<'a, 'p> {
Ok(self.node_metadata(node_id)?.persistent_metadata.pinned)
}

pub fn is_collapsed(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
let node_metadata = self.node_metadata(node_id)?;
let collapsed = node_metadata.persistent_metadata.collapsed.unwrap_or_else(|| self.implementation_name(node_id) == "Merge");

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.

P3: This compares the implementation name against a hardcoded "Merge" literal, even though the same PR adds a public MERGE_NODE_IDENTIFIER: &str = "Merge" constant in document_node_definitions.rs and the merge node definition now uses it. Reusing that constant here keeps the merge-node identity in one place so a future rename of the node identifier doesn't silently break the default-collapsed behavior for merge nodes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/utility_types/network_interface/view.rs, line 189:

<comment>This compares the implementation name against a hardcoded `"Merge"` literal, even though the same PR adds a public `MERGE_NODE_IDENTIFIER: &str = "Merge"` constant in `document_node_definitions.rs` and the merge node definition now uses it. Reusing that constant here keeps the merge-node identity in one place so a future rename of the node identifier doesn't silently break the default-collapsed behavior for merge nodes.</comment>

<file context>
@@ -184,6 +184,12 @@ impl<'a, 'p> NetworkView<'a, 'p> {
 
+	pub fn is_collapsed(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
+		let node_metadata = self.node_metadata(node_id)?;
+		let collapsed = node_metadata.persistent_metadata.collapsed.unwrap_or_else(|| self.implementation_name(node_id) == "Merge");
+		Ok(collapsed)
+	}
</file context>

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.

P3: The fallback identity check self.implementation_name(node_id) == "Merge" duplicates the canonical is_merge() method defined just above in this same impl block, which compares the node's DefinitionIdentifier against DefinitionIdentifier::Network("Merge"). Using the human-display string for identity is fragile and redundant: it breaks if the Merge node's display name ever differs from its registry key (the type docs note network display names "don't necessarily have to be the same"), and it re-implements logic already centralized in is_merge(). Replace with self.is_merge(node_id).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/utility_types/network_interface/view.rs, line 197:

<comment>The fallback identity check `self.implementation_name(node_id) == "Merge"` duplicates the canonical `is_merge()` method defined just above in this same impl block, which compares the node's `DefinitionIdentifier` against `DefinitionIdentifier::Network("Merge")`. Using the human-display string for identity is fragile and redundant: it breaks if the Merge node's display name ever differs from its registry key (the type docs note network display names "don't necessarily have to be the same"), and it re-implements logic already centralized in `is_merge()`. Replace with `self.is_merge(node_id)`.</comment>

<file context>
@@ -192,6 +192,12 @@ impl<'a, 'p> NetworkView<'a, 'p> {
 
+	pub fn is_collapsed(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
+		let node_metadata = self.node_metadata(node_id)?;
+		let collapsed = node_metadata.persistent_metadata.collapsed.unwrap_or_else(|| self.implementation_name(node_id) == "Merge");
+		Ok(collapsed)
+	}
</file context>

Ok(collapsed)
}

pub fn is_visible(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(self.node(node_id)?.visible)
}
Expand Down
Loading