Guide System V2 - Track-Based Layout Control
Technical specification for the improved divider guide system that controls only grid/flex track sizing without affecting section count
Guide System V2 - Track-Based Layout Control
Overview
The Guide System V2 is a modern, intuitive way to control canvas layout track sizing (grid rows/columns or flexbox proportions) without automatically adding or removing sections. This decouples layout structure from content organization, giving users precise control over both aspects independently.
Key Principles
1. Guides Control Track Sizing Only
- Horizontal guides → row heights (grid) or flex-basis percentages (flexbox)
- Vertical guides → column widths (grid only)
- Section count is controlled separately via "Add Section" / "Remove" buttons
- Guides do NOT generate new sections
2. Live, Real-Time Updates
- As you drag a guide, the canvas layout adjusts instantly
- No "confirm" step required—changes are applied in real-time
- Debounced updates (60ms) for smooth performance
- Snapshot/restore mechanism allows canceling all changes
3. Mode Parity
- Flexbox Mode: Horizontal guides adjust
flex-basispercentages for each section - Grid Mode: Both horizontal and vertical guides define
grid-template-rowsandgrid-template-columns - Switching between modes preserves existing sections
User Workflow
Entering Divide Mode
- Click "Divide" in the canvas toolbar
- The canvas saves a snapshot of the current layout state
- Existing sections remain visible and in place
- A divider toolbar appears at the top with controls
Adding Guides
Click "Add Horizontal" to add a horizontal guide
- Guide is placed at an optimal position (evenly distributed)
- Immediately updates row heights or flex proportions
Click "Add Vertical" (Grid mode only) to add a vertical guide
- Guide is placed at an optimal position
- Immediately updates column widths
Adjusting Guides
Drag the guide handle to reposition
- Handle is a modern, gradient-styled button with position label
- Line thickness increases to 4px while dragging for clear feedback
- Canvas tracks adjust in real-time (debounced at 60ms)
Remove a guide by clicking the × button on the handle
- Layout recalculates immediately to redistribute space
Exiting Divide Mode
Done (Green button):
- Keeps all guide changes
- Persists layout to storage
- Exits divide mode
Restore & Exit (Red button):
- Reverts to the snapshot taken when entering divide mode
- Discards all guide adjustments
- Exits divide mode
Technical Implementation
Architecture
GridCanvasComponent
├── dividerState: WritableSignal<DividerState>
├── divideModeSnapshot: LayoutConfiguration | null
├── applyDividerLayout(reason: string): void
├── applyGridTracksFromGuides(): void
├── applyFlexboxFromGuides(): void
├── getDividerStops(dividers, maxSize): number[]
└── convertPixelListToFractions(pixelValues): string[]
DividerToolComponent
├── Visual rendering of guide line
├── Draggable handle with position display
└── Real-time position feedback
Key Methods
applyDividerLayout(reason: string)
Main orchestrator:
- Checks if divide mode is active
- If no guides exist, restores snapshot
- Otherwise delegates to mode-specific methods:
applyGridTracksFromGuides()for grid modeapplyFlexboxFromGuides()for flexbox mode
- Emits layout changes and schedules persistence
applyGridTracksFromGuides(state, config, canvasSize)
Grid mode handler:
- Calculates row stops from horizontal guides:
[0, pos1, pos2, ..., height] - Calculates column stops from vertical guides:
[0, pos1, pos2, ..., width] - Converts pixel distances to fractional units (e.g.,
0.333fr,0.667fr) - Updates
config.grid.rowsandconfig.grid.columns - Ensures existing sections have valid
gridplacement (auto-assigns if missing) - Does NOT add or remove sections
applyFlexboxFromGuides(state, config, canvasSize)
Flexbox mode handler:
- Calculates row stops from horizontal guides (vertical guides ignored in flexbox)
- Determines segment sizes between each pair of stops
- Calculates percentage for each segment relative to total height
- Assigns
flex-basisto existing sections based on segment index - If more sections than segments, uses the last segment's proportion
- Does NOT add or remove sections
getDividerStops(dividers, maxSize): number[]
Utility to get all stop positions:
- Sorts divider positions in ascending order
- Clamps to
[0, maxSize] - Returns:
[0, ...divider positions..., maxSize]
convertPixelListToFractions(pixelValues): string[]
Converts absolute pixel sizes to proportional fractional units:
- Parses pixel values (e.g.,
["100px", "200px"]) - Sums total
- Returns fractional units (e.g.,
["0.333fr", "0.667fr"]) - Ensures layout is responsive and fluid
Visual Design
Guide Line Styling
.divider-line-visual {
background: linear-gradient(90deg,
rgba(236, 72, 153, 0.8) 0%,
rgba(236, 72, 153, 1) 50%,
rgba(236, 72, 153, 0.8) 100%);
box-shadow:
0 0 12px rgba(236, 72, 153, 0.6),
inset 0 0 8px rgba(255, 255, 255, 0.3);
// 3px width/height normally
// 4px when dragging for emphasis
}
Guide Handle Styling
.divider-handle {
background: linear-gradient(135deg,
rgba(236, 72, 153, 0.95) 0%,
rgba(236, 72, 153, 1) 100%);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 10px;
padding: 10px 16px;
box-shadow:
0 4px 16px rgba(236, 72, 153, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
backdrop-filter: blur(8px);
cursor: grab;
&:active {
cursor: grabbing;
background: linear-gradient(135deg,
rgba(139, 92, 246, 1) 0%,
rgba(124, 58, 237, 1) 100%);
}
}
Smooth Transitions
Both the canvas container and sections use cubic-bezier easing:
transition:
grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1),
grid-template-columns 0.3s cubic-bezier(0.4, 0, 0.2, 1),
width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
Edge Cases & Considerations
1. No Guides
- If all guides are removed, layout reverts to snapshot
- User can manually adjust by re-adding guides or using "Restore & Exit"
2. More Sections than Tracks
- Grid Mode: Sections auto-place according to CSS grid's auto-placement algorithm
- Flexbox Mode: Sections beyond the number of horizontal segments use the last segment's proportion
3. Fewer Sections than Tracks
- Grid Mode: Unused tracks remain in the grid definition (empty cells)
- Flexbox Mode: Only occupied segments affect layout
4. Switching Modes with Guides Active
- When switching from Grid → Flexbox: Vertical guides are ignored, horizontal guides apply
- When switching from Flexbox → Grid: Both horizontal and vertical guides apply
- Section count remains unchanged
5. Minimum Edge Spacing
minEdgeSpacingprop (default: 24px) prevents guides from being too close to edges- Ensures handles remain clickable and layout remains visually balanced
Comparison: V1 vs. V2
| Aspect | V1 (Old) | V2 (New) |
|---|---|---|
| Guide Purpose | Generate sections | Control track sizing only |
| Section Count | Auto-generated from guides | User-controlled separately |
| Confirmation | Required ("Confirm & Generate") | Live updates, "Done" to keep |
| Restore | "Cancel" exits without changes | "Restore & Exit" reverts to snapshot |
| Visual Feedback | Basic lines | Modern gradients, shadows, glows |
| Handle Design | Simple buttons | Gradient badges with position labels |
| Mode Parity | Flexbox only partially supported | Full parity for Grid and Flexbox |
| Complexity | High (too many generated sections) | Low (intuitive track resizing) |
Code Examples
Adding a Horizontal Guide
addHorizontalDivider(): void {
if (this.canvasMode() !== 'divide') return;
const canvasSize = this.getCanvasSize();
const existing = this.dividerState().horizontal;
const position = this.dividerManager.getOptimalPosition('horizontal', existing, canvasSize);
const divider = this.dividerManager.addDivider('horizontal', position, canvasSize);
this.dividerState.update(state => ({
...state,
horizontal: [...state.horizontal, divider]
}));
this.scheduleDividerLayoutUpdate('add-horizontal');
}
Handling Guide Position Change
onDividerPositionChanged(event: DividerPositionChangeEvent): void {
this.dividerState.update(state => {
const horizontal = state.horizontal.map(d =>
d.id === event.id
? { ...d, position: event.position, positionPercent: event.positionPercent }
: d
);
const vertical = state.vertical.map(d =>
d.id === event.id
? { ...d, position: event.position, positionPercent: event.positionPercent }
: d
);
return { ...state, horizontal, vertical };
});
this.scheduleDividerLayoutUpdate('move-divider');
}
Applying Grid Tracks
private applyGridTracksFromGuides(
state: DividerState,
config: LayoutConfiguration,
canvasSize: { width: number; height: number }
): void {
const rowStops = this.getDividerStops(state.horizontal, canvasSize.height);
const colStops = this.getDividerStops(state.vertical, canvasSize.width);
const rows = rowStops.slice(1).map((stop, i) => `${stop - rowStops[i]}px`);
const columns = colStops.slice(1).map((stop, i) => `${stop - colStops[i]}px`);
config.grid = {
...(config.grid ?? {}),
rows: this.convertPixelListToFractions(rows),
columns: this.convertPixelListToFractions(columns),
gap: config.grid?.gap ?? '16px'
};
// Ensure sections have grid placement
config.sections.forEach((section, index) => {
if (!section.grid) {
const numCols = columns.length;
section.grid = {
row: Math.floor(index / numCols) + 1,
col: (index % numCols) + 1,
rowSpan: 1,
colSpan: 1
};
}
section.flexbox = undefined;
section.updatedAt = new Date();
});
}
Best Practices
For Users
- Start with sections first: Add the number of sections you want via "Add Section"
- Then adjust spacing: Enter Divide mode and add guides to control proportions
- Drag guides freely: Live feedback shows immediate results
- Remove guides easily: Click the × on any handle
- Cancel if needed: Use "Restore & Exit" to undo all guide changes
For Developers
- Debounce updates: Use
scheduleDividerLayoutUpdate()to avoid excessive re-renders - Always update sections immutably: Use
structuredClone()for deep copies - Call
cdr.markForCheck(): Ensure Angular's OnPush strategy detects changes - Preserve section count: Never add/remove sections in
applyDividerLayout() - Handle missing grid placement: Auto-assign sensible defaults if
section.gridisundefined
Future Enhancements
- Snap-to-section edges: Guides could snap to existing section boundaries for precision
- Multi-select guides: Drag multiple guides simultaneously
- Guide presets: Save and load common guide configurations (e.g., "3-column layout", "hero + 2-row")
- Visual grid overlay: Show faint grid lines at guide intersections for reference
- Keyboard shortcuts: Arrow keys to nudge guides by 1px/10px increments
Related Documentation
- MODULAR-UNIVERSE-CUTTING-MECHANIC-SPEC.md - Original cutting mechanic (V1)
- DRAG-DROP-SYSTEM.md - Custom drag-and-drop implementation
- @libs-ux/drag-drop README - Drag-drop library
Changelog
V2 (November 20, 2025)
- ✅ Guides now control only track sizing, not section count
- ✅ Live, real-time layout updates (no confirm step required)
- ✅ Snapshot/restore mechanism for canceling changes
- ✅ Modern visual design: gradients, shadows, glows
- ✅ Improved handle styling with position labels
- ✅ Smooth cubic-bezier transitions for layout changes
- ✅ Full parity between Flexbox and Grid modes
- ✅ Clear hint text: "Guides control track sizing only"
V1 (November 18, 2025)
- Initial implementation with section generation
- Basic divider tool with drag support
- Confirmation step required