There are two related bugs affecting node display and layout:
- Pin Positioning Bug: When nodes are created, deleted, and undone, pins don't position correctly until the node is manually resized
- Node Sizing Bug: When loading nodes with size smaller than minimum required for GUI + pins, the node is crushed and GUI elements are compressed
- Located in
src/node.py_update_layout()method (lines 218-269) - Pin positioning is calculated correctly but visual update doesn't trigger properly
pin.update_label_pos()is called but pin visual refresh may not occur- Issue manifests during undo operations when nodes are recreated from serialized state
- Located in
src/node.py_calculate_minimum_height()andfit_size_to_content()methods - Minimum size calculation occurs but enforcement is inconsistent
- During undo restoration in
src/commands/node_commands.pyDeleteNodeCommand.undo() (lines 250-384) - Size is set multiple times but may not respect GUI content minimum requirements
Location: src/pin.py
- Issue:
update_label_pos()method (lines 61-68) only updates label position, not pin visual state - Fix: Add explicit pin visual refresh after position updates
- Implementation:
- Add
update_visual_state()method to Pin class - Call
self.update()to trigger Qt repaint - Ensure pin connections are also updated (
update_connections())
- Add
Location: src/node.py _update_layout() method
- Issue: Layout calculation is correct but visual update chain is incomplete
- Fix: Ensure complete visual refresh after layout changes
- Implementation:
- Call
self.prepareGeometryChange()before any position changes - Force pin visual updates after positioning
- Trigger
self.update()to refresh node visual state - Update all pin connections after layout changes
- Call
Location: src/node.py _calculate_minimum_height() and fit_size_to_content()
- Issue: Minimum size calculation doesn't account for all content properly
- Fix: Improve minimum size calculation and enforcement
- Implementation:
- Include proxy widget minimum size requirements
- Add safety margins for GUI content
- Ensure width calculation includes pin labels and content
- Prevent size from being set below calculated minimum
Location: src/node.py
- Issue: No comprehensive method to calculate absolute minimum node size for all content
- Fix: Create robust minimum size calculation that accounts for all node components
- Implementation:
- Add
calculate_absolute_minimum_size()method that returns (min_width, min_height) - Calculate minimum width based on:
- Title text width
- Longest pin label width (input and output sides)
- GUI content minimum width
- Minimum node padding and margins
- Calculate minimum height based on:
- Title bar height
- Pin area height (max of input/output pin counts × pin_spacing)
- GUI content minimum height
- Required spacing and margins
- Include safety margins for visual clarity
- Account for resize handle area
- Add
Location: src/file_operations.py and node creation/loading functions
- Issue: Nodes can be loaded with sizes smaller than their minimum requirements, causing layout issues
- Fix: Validate and correct node sizes during loading operations
- Implementation:
- Add validation check in node loading/deserialization functions
- Call
calculate_absolute_minimum_size()for each loaded node - Compare loaded size against calculated minimum size
- If loaded size is smaller than minimum, automatically adjust to minimum
- Log size corrections for debugging purposes
- Apply this validation in:
- Graph file loading (.md and .json formats)
- Node creation from templates
- Import operations
- Any node deserialization process
Location: src/commands/node_commands.py DeleteNodeCommand.undo()
- Issue: Node recreation process doesn't properly trigger layout updates
- Fix: Ensure proper initialization sequence during node restoration
- Implementation:
- Call
fit_size_to_content()after all properties are set - Force
_update_layout()after pin creation - Add explicit visual refresh after restoration
- Ensure GUI state is applied before size calculations
- Validate restored size against minimum requirements using new
calculate_absolute_minimum_size()
- Call
Location: src/commands/node_commands.py
- Issue: No validation that restored node layout is correct
- Fix: Add validation and correction step after node restoration
- Implementation:
- Check if node size meets minimum requirements using
calculate_absolute_minimum_size() - Verify pin positions are within node bounds
- Validate GUI content fits within allocated space
- Force layout recalculation if validation fails
- Apply minimum size corrections if necessary
- Check if node size meets minimum requirements using
Location: src/node.py
- Issue: No centralized way to force complete layout refresh
- Fix: Create comprehensive refresh method
- Implementation:
- Add
refresh_layout()method to Node class - Include pin positioning, size validation, and visual updates
- Call from critical points: after undo, after loading, after code changes
- Incorporate minimum size validation using
calculate_absolute_minimum_size() - Auto-correct size if it's below minimum requirements
- Add
Location: src/node.py _update_layout() method
- Issue: Proxy widget sizing logic is fragile (lines 256-264)
- Fix: Make widget sizing more robust
- Implementation:
- Calculate content area more precisely
- Add minimum content height enforcement
- Handle edge cases where content is larger than available space
- Goal: Ensure all components work together correctly
- Tests:
- Create node → delete → undo sequence
- Load graphs with small node sizes
- Resize nodes with different content types
- Test with nodes containing GUI elements
- Goal: Ensure layout updates don't impact performance
- Implementation:
- Batch layout updates when possible
- Avoid redundant calculations
- Use lazy evaluation for expensive operations
- Task 1.4: Comprehensive Minimum Size Calculation System
- Task 2.1: Add Minimum Size Validation on Node Loading
- Task 1.2: Enhanced Node Layout Update Process
- Task 2.2: Improved Node Restoration Process
- Task 1.1: Pin Position Update Mechanism
- Task 1.3: Minimum Size Enforcement
- Task 3.1: Layout Refresh Method
- Task 2.3: Post-Restoration Validation
- Task 3.2: Content Widget Sizing Improvements
- Task 4.2: Performance Optimization
- Pins will position correctly immediately after undo operations
- Nodes will maintain proper minimum size during all operations
- GUI elements will never be crushed or compressed
- Nodes loaded from files will automatically resize to minimum requirements if saved too small
- Comprehensive minimum size calculation prevents layout issues across all node types
- More robust layout calculation system
- Better separation of concerns between layout and visual updates
- Improved error handling and validation
- Eliminated need for manual node resizing to fix layout
- Consistent node appearance across all operations
- More reliable undo/redo functionality
The calculate_absolute_minimum_size() method should implement the following logic:
def calculate_absolute_minimum_size(self) -> tuple[int, int]:
"""Calculate the absolute minimum size needed for this node's content."""
# Base measurements
title_height = 32
pin_spacing = 25
pin_margin_top = 15
node_padding = 10
resize_handle_size = 15
# Calculate minimum width
title_width = self._title_item.boundingRect().width() + 20 # Title + padding
# Pin label widths (find longest on each side)
max_input_label_width = max([pin.label.boundingRect().width()
for pin in self.input_pins] or [0])
max_output_label_width = max([pin.label.boundingRect().width()
for pin in self.output_pins] or [0])
pin_label_width = max_input_label_width + max_output_label_width + 40 # Labels + pin spacing
# GUI content minimum width
gui_min_width = 0
if self.content_container:
gui_min_width = self.content_container.minimumSizeHint().width()
min_width = max(
self.base_width, # Default base width
title_width,
pin_label_width,
gui_min_width + node_padding
)
# Calculate minimum height
max_pins = max(len(self.input_pins), len(self.output_pins))
pin_area_height = (max_pins * pin_spacing) if max_pins > 0 else 0
# GUI content minimum height
gui_min_height = 0
if self.content_container:
gui_min_height = self.content_container.minimumSizeHint().height()
min_height = (title_height +
pin_margin_top +
pin_area_height +
gui_min_height +
resize_handle_size +
node_padding)
return (min_width, min_height)During node loading, implement this validation:
def validate_and_correct_node_size(node_data: dict) -> dict:
"""Validate node size against minimum requirements and correct if needed."""
# Create temporary node to calculate minimum size
temp_node = create_node_from_data(node_data)
min_width, min_height = temp_node.calculate_absolute_minimum_size()
loaded_width = node_data.get('size', [200, 150])[0]
loaded_height = node_data.get('size', [200, 150])[1]
corrected_width = max(loaded_width, min_width)
corrected_height = max(loaded_height, min_height)
if corrected_width != loaded_width or corrected_height != loaded_height:
print(f"Node '{node_data['title']}' size corrected from "
f"{loaded_width}x{loaded_height} to {corrected_width}x{corrected_height}")
node_data['size'] = [corrected_width, corrected_height]
return node_data- Always call
prepareGeometryChange()before modifying positions/sizes - Use consistent method naming:
update_*()for calculations,refresh_*()for visual updates - Include proper error handling and fallback behavior
- Follow existing code style and commenting patterns
Important: These issues are highly dependent on GUI rendering, Qt layout systems, and real-time visual updates. Traditional unit tests are insufficient for debugging these problems.
- Add comprehensive debug prints throughout the layout and sizing methods
- Focus on key methods:
Node._update_layout()- track pin positioning calculationsNode.calculate_absolute_minimum_size()- verify size calculationsNode.fit_size_to_content()- monitor size adjustmentsPin.update_label_pos()- track pin position updatesDeleteNodeCommand.undo()- monitor restoration sequence
def _update_layout(self):
print(f"DEBUG: _update_layout() called for node '{self.title}'")
print(f"DEBUG: Current size: {self.width}x{self.height}")
print(f"DEBUG: Pin counts - input: {len(self.input_pins)}, output: {len(self.output_pins)}")
# ... existing layout code ...
for i, pin in enumerate(self.input_pins):
print(f"DEBUG: Input pin {i} positioned at {pin.pos()}")
print(f"DEBUG: _update_layout() completed")-
Size Validation Points:
- Before and after
fit_size_to_content() - During node loading/restoration
- When size constraints are applied
- Before and after
-
Pin Positioning Points:
- Before and after pin position calculations
- During visual updates
- After undo operations
-
Layout Trigger Points:
- When
_update_layout()is called - During GUI widget creation/rebuilding
- After property changes
- When
- Run the application with debug prints enabled
- Perform the exact user scenario: create node → delete → undo
- Monitor console output for layout sequence issues
- Manually resize node to trigger correct layout, compare debug output
- Use debug prints to identify where the layout chain breaks
- During Development: Use extensive debug prints to trace execution flow
- Conditional Debugging: Consider using a debug flag to enable/disable prints
DEBUG_LAYOUT = True # Set to False for production
def _update_layout(self):
if DEBUG_LAYOUT:
print(f"DEBUG: _update_layout() called for node '{self.title}'")
# ... rest of method- Post-Fix Cleanup: Remove or disable debug prints once issues are resolved
- Keep Key Diagnostics: Retain essential debug prints that could help with future issues
While debug prints are primary, these tests support the debugging process:
- Create unit tests for layout calculation methods (pure calculation testing)
- Add integration tests for undo/redo scenarios
- Include visual regression tests for node appearance
- Test with various node types: code-only, GUI-enabled, different sizes
- New minimum size tests:
- Test
calculate_absolute_minimum_size()with various content types - Load graphs with intentionally small node sizes and verify auto-correction
- Test nodes with complex GUI content (many widgets, large content)
- Verify minimum size calculations with different pin configurations
- Test edge cases: no pins, many pins, long pin labels, wide titles
- Test
This plan addresses both immediate bugs and underlying architectural issues that could cause similar problems in the future. The proposed changes create a more robust foundation for node layout management while maintaining backward compatibility with existing functionality.
Regular testing of the undo/redo system and node layout should be performed, especially when making changes to the node system, pin system, or command system.