Modular Universe Evolution - Cutting Mechanic & Storage Design
Complete specification for canvas cutting mechanic and storage architecture
✨ Modular Universe Evolution - Cutting Mechanic & Storage Design
Last Updated: November 14, 2025
Status: 🎯 MASTER SPECIFICATION
Purpose: Revolutionary canvas cutting mechanic implementation plan
🎯 EXECUTIVE SUMMARY
This document specifies the revolutionary canvas cutting mechanic that will make modular-universe the most powerful, intuitive universe builder on the planet.
Vision: Beautiful empty canvas → User adds divider lines → Drag to position → Confirm → Sections auto-generated → Each section becomes a module
Current State: ✅ Foundation exists (grid-canvas with modes)
Next Evolution: ⚡ Add visual cutting mechanic with divider lines
📊 CURRENT STATE REVIEW
What's Already Implemented ✅
From grid-canvas.component.ts:
// Mode system
type CanvasMode = 'select' | 'add' | 'resize' | 'reorder';
// Layout support
interface LayoutConfiguration {
type: 'flexbox' | 'grid';
flexbox?: FlexboxConfig;
grid?: GridConfig;
sections: SectionConfig[];
}
// Section management
- addSection(position?, referenceId?)
- removeSection(sectionId)
- updateSection(section)
- resizeSection(sectionId, handle, dimensions)
- moveSectionUpDown(sectionId, direction)
// Storage integration
- loadLayoutConfiguration()
- saveLayoutConfiguration()
From section-config-panel.component.ts:
// Section configuration
interface SectionConfig {
id: string;
name: string;
contentType: 'empty' | 'module' | 'page' | 'widget-grid' | 'custom';
assignedModuleId?: string;
flexbox?: FlexboxSectionConfig;
grid?: GridSectionConfig;
positioning: PositioningConfig;
behavior: BehaviorConfig;
visibility: VisibilityConfig;
// ... more
}
From modular-universe-builder.component.ts:
// Orchestrator
- Combines grid-canvas + section-config-panel
- Manages module assignment
- Handles universe/galaxy context
What's Missing ⚠️
- Divider Lines - Visual horizontal/vertical lines
- Cutting UX - Drag dividers to slice canvas
- Auto-Section Generation - Calculate sections from dividers
- Template Presets - Common layouts (header/content/footer)
- Visual Feedback - Show section boundaries during cutting
🎨 THE CUTTING MECHANIC
User Experience Flow
STEP 1: Empty Canvas
┌─────────────────────────────────────────┐
│ │
│ │
│ Empty Canvas │
│ Click to add dividers │
│ │
│ │
└─────────────────────────────────────────┘
STEP 2: Add Horizontal Divider
┌─────────────────────────────────────────┐
│ │
│ │
├─────────────────────────────────────────┤ ← Draggable line
│ │
│ │
│ │
└─────────────────────────────────────────┘
STEP 3: Add Vertical Divider
┌──────────────────┬──────────────────────┐
│ │ │
│ │ │
├──────────────────┼──────────────────────┤
│ │ │
│ │ │
│ │ │
└──────────────────┴──────────────────────┘
STEP 4: Confirm → Sections Generated
┌──────────────────┬──────────────────────┐
│ Section 1 │ Section 2 │
│ (80px fixed) │ (1fr flexible) │
├──────────────────┼──────────────────────┤
│ Section 3 │ Section 4 │
│ (1fr flexible) │ (1fr flexible) │
│ │ │
└──────────────────┴──────────────────────┘
Each section = Module with configurable behavior
Mode Addition
New Mode: 'divide'
// Updated canvas-mode.types.ts
export type CanvasMode =
| 'select' // Select and configure sections
| 'add' // Add new sections
| 'resize' // Resize sections
| 'reorder' // Reorder sections
| 'divide'; // NEW: Add divider lines to cut canvas
export const CANVAS_MODES: Record<CanvasMode, CanvasModeConfig> = {
// ... existing modes
divide: {
id: 'divide',
name: 'Divide Canvas',
description: 'Add horizontal or vertical lines to slice the canvas into sections',
icon: 'cut',
cursor: 'crosshair',
allowSelection: false,
allowResize: false,
allowReorder: false,
allowDivide: true // NEW property
}
};
Divider Data Model
// models/divider.types.ts
export interface Divider {
id: string;
type: 'horizontal' | 'vertical';
position: number; // Pixels from top (horizontal) or left (vertical)
positionPercent: number; // 0-100 for responsive
isDragging: boolean;
isTemporary: boolean; // True until confirmed
createdAt: Date;
}
export interface DividerState {
horizontal: Divider[];
vertical: Divider[];
isActive: boolean; // True when in divide mode
canConfirm: boolean; // True when valid layout
}
export interface GeneratedSection {
id: string;
name: string;
bounds: {
top: number;
left: number;
width: number;
height: number;
};
gridArea?: {
row: number;
col: number;
rowSpan: number;
colSpan: number;
};
flexbox?: {
flexGrow: number;
flexShrink: number;
flexBasis: string;
};
}
🛠️ IMPLEMENTATION SPECIFICATION
Component: Divider Tool
// components/divider-tool/divider-tool.component.ts
@Component({
selector: 'lib-divider-tool',
standalone: true,
imports: [CommonModule, CdkDrag],
template: `
<div
class="divider-line"
[class.horizontal]="type === 'horizontal'"
[class.vertical]="type === 'vertical'"
[class.dragging]="isDragging"
[style.top.px]="type === 'horizontal' ? position : 0"
[style.left.px]="type === 'vertical' ? position : 0"
[style.width]="type === 'horizontal' ? '100%' : '2px'"
[style.height]="type === 'vertical' ? '100%' : '2px'"
cdkDrag
[cdkDragBoundary]="'.canvas-container'"
(cdkDragStarted)="onDragStart()"
(cdkDragMoved)="onDragMove($event)"
(cdkDragEnded)="onDragEnd()"
>
<div class="divider-handle">
<span class="divider-label">{{ position }}px</span>
<button (click)="onRemove.emit(divider.id)" class="remove-btn">×</button>
</div>
</div>
`,
styleUrls: ['./divider-tool.component.scss']
})
export class DividerToolComponent {
@Input({ required: true }) divider!: Divider;
@Input({ required: true }) canvasSize!: { width: number; height: number };
@Output() positionChanged = new EventEmitter<{ id: string; position: number }>();
@Output() onRemove = new EventEmitter<string>();
isDragging = signal(false);
get type() {
return this.divider.type;
}
get position() {
return this.divider.position;
}
onDragStart() {
this.isDragging.set(true);
}
onDragMove(event: CdkDragMove) {
const delta = this.type === 'horizontal'
? event.distance.y
: event.distance.x;
const maxPosition = this.type === 'horizontal'
? this.canvasSize.height
: this.canvasSize.width;
const newPosition = Math.max(50, Math.min(maxPosition - 50, this.position + delta));
this.positionChanged.emit({
id: this.divider.id,
position: newPosition
});
}
onDragEnd() {
this.isDragging.set(false);
}
}
Service: Divider Manager
// services/divider-manager.service.ts
@Injectable()
export class DividerManagerService {
/**
* Add divider line to canvas
*/
addDivider(
type: 'horizontal' | 'vertical',
position: number,
canvasSize: { width: number; height: number }
): Divider {
const divider: Divider = {
id: `divider_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
type,
position,
positionPercent: this.calculatePercent(position, type, canvasSize),
isDragging: false,
isTemporary: true,
createdAt: new Date()
};
return divider;
}
/**
* Update divider position
*/
updateDividerPosition(
divider: Divider,
newPosition: number,
canvasSize: { width: number; height: number }
): Divider {
return {
...divider,
position: newPosition,
positionPercent: this.calculatePercent(newPosition, divider.type, canvasSize)
};
}
/**
* Generate sections from dividers
*/
generateSectionsFromDividers(
dividers: DividerState,
canvasSize: { width: number; height: number },
layoutType: 'flexbox' | 'grid'
): GeneratedSection[] {
const horizontal = [...dividers.horizontal].sort((a, b) => a.position - b.position);
const vertical = [...dividers.vertical].sort((a, b) => a.position - b.position);
if (layoutType === 'grid') {
return this.generateGridSections(horizontal, vertical, canvasSize);
} else {
return this.generateFlexboxSections(horizontal, vertical, canvasSize);
}
}
/**
* Generate CSS Grid sections
*/
private generateGridSections(
horizontal: Divider[],
vertical: Divider[],
canvasSize: { width: number; height: number }
): GeneratedSection[] {
const sections: GeneratedSection[] = [];
// Calculate row heights
const rows = this.calculateGridRows(horizontal, canvasSize.height);
// Calculate column widths
const columns = this.calculateGridColumns(vertical, canvasSize.width);
// Generate section for each grid cell
let sectionIndex = 0;
for (let row = 0; row < rows.length; row++) {
for (let col = 0; col < columns.length; col++) {
sectionIndex++;
const section: GeneratedSection = {
id: `section_${Date.now()}_${sectionIndex}`,
name: `Section ${sectionIndex}`,
bounds: {
top: this.calculateRowStart(row, rows, horizontal),
left: this.calculateColStart(col, columns, vertical),
width: columns[col],
height: rows[row]
},
gridArea: {
row: row + 1,
col: col + 1,
rowSpan: 1,
colSpan: 1
}
};
sections.push(section);
}
}
return sections;
}
/**
* Generate Flexbox sections
*/
private generateFlexboxSections(
horizontal: Divider[],
vertical: Divider[],
canvasSize: { width: number; height: number }
): GeneratedSection[] {
// For flexbox, sections are created sequentially
// Use horizontal dividers to determine section order
const sections: GeneratedSection[] = [];
const sortedDividers = horizontal.sort((a, b) => a.position - b.position);
let previousPosition = 0;
sortedDividers.forEach((divider, index) => {
const section: GeneratedSection = {
id: `section_${Date.now()}_${index + 1}`,
name: `Section ${index + 1}`,
bounds: {
top: previousPosition,
left: 0,
width: canvasSize.width,
height: divider.position - previousPosition
},
flexbox: {
flexGrow: 0,
flexShrink: 0,
flexBasis: `${divider.position - previousPosition}px`
}
};
sections.push(section);
previousPosition = divider.position;
});
// Add final section
if (previousPosition < canvasSize.height) {
sections.push({
id: `section_${Date.now()}_${sections.length + 1}`,
name: `Section ${sections.length + 1}`,
bounds: {
top: previousPosition,
left: 0,
width: canvasSize.width,
height: canvasSize.height - previousPosition
},
flexbox: {
flexGrow: 1,
flexShrink: 1,
flexBasis: 'auto'
}
});
}
return sections;
}
/**
* Calculate grid rows from horizontal dividers
*/
private calculateGridRows(dividers: Divider[], totalHeight: number): number[] {
if (dividers.length === 0) return [totalHeight];
const rows: number[] = [];
let previousPosition = 0;
dividers.forEach(divider => {
rows.push(divider.position - previousPosition);
previousPosition = divider.position;
});
// Add final row
rows.push(totalHeight - previousPosition);
return rows;
}
/**
* Calculate grid columns from vertical dividers
*/
private calculateGridColumns(dividers: Divider[], totalWidth: number): number[] {
if (dividers.length === 0) return [totalWidth];
const columns: number[] = [];
let previousPosition = 0;
dividers.forEach(divider => {
columns.push(divider.position - previousPosition);
previousPosition = divider.position;
});
// Add final column
columns.push(totalWidth - previousPosition);
return columns;
}
/**
* Calculate percentage position
*/
private calculatePercent(
position: number,
type: 'horizontal' | 'vertical',
canvasSize: { width: number; height: number }
): number {
const max = type === 'horizontal' ? canvasSize.height : canvasSize.width;
return (position / max) * 100;
}
}
Enhanced Grid Canvas Component
// grid-canvas.component.ts (additions)
export class GridCanvasComponent {
// ... existing code
// NEW: Divider state
readonly dividerState = signal<DividerState>({
horizontal: [],
vertical: [],
isActive: false,
canConfirm: false
});
readonly activeDividers = computed(() => {
const state = this.dividerState();
return [...state.horizontal, ...state.vertical];
});
private dividerManager = inject(DividerManagerService);
/**
* Enter divide mode
*/
enterDivideMode(): void {
this.setCanvasMode('divide');
this.dividerState.update(state => ({
...state,
isActive: true
}));
}
/**
* Add horizontal divider
*/
addHorizontalDivider(): void {
if (this.canvasMode() !== 'divide') return;
const canvasSize = this.getCanvasSize();
const position = canvasSize.height / 2; // Default to middle
const divider = this.dividerManager.addDivider('horizontal', position, canvasSize);
this.dividerState.update(state => ({
...state,
horizontal: [...state.horizontal, divider],
canConfirm: true
}));
}
/**
* Add vertical divider
*/
addVerticalDivider(): void {
if (this.canvasMode() !== 'divide') return;
const canvasSize = this.getCanvasSize();
const position = canvasSize.width / 2; // Default to middle
const divider = this.dividerManager.addDivider('vertical', position, canvasSize);
this.dividerState.update(state => ({
...state,
vertical: [...state.vertical, divider],
canConfirm: true
}));
}
/**
* Update divider position
*/
onDividerPositionChanged(event: { id: string; position: number }): void {
const canvasSize = this.getCanvasSize();
this.dividerState.update(state => {
const horizontal = state.horizontal.map(d =>
d.id === event.id
? this.dividerManager.updateDividerPosition(d, event.position, canvasSize)
: d
);
const vertical = state.vertical.map(d =>
d.id === event.id
? this.dividerManager.updateDividerPosition(d, event.position, canvasSize)
: d
);
return { ...state, horizontal, vertical };
});
}
/**
* Remove divider
*/
removeDivider(dividerId: string): void {
this.dividerState.update(state => ({
...state,
horizontal: state.horizontal.filter(d => d.id !== dividerId),
vertical: state.vertical.filter(d => d.id !== dividerId),
canConfirm: (state.horizontal.length + state.vertical.length) > 1
}));
}
/**
* Confirm dividers and generate sections
*/
confirmDividers(): void {
const state = this.dividerState();
if (!state.canConfirm) return;
const canvasSize = this.getCanvasSize();
const layoutType = this.layoutConfig().type;
// Generate sections from dividers
const generatedSections = this.dividerManager.generateSectionsFromDividers(
state,
canvasSize,
layoutType
);
// Convert to SectionConfig
const sections: SectionConfig[] = generatedSections.map(gs => ({
id: gs.id,
name: gs.name,
contentType: 'empty',
flexbox: gs.flexbox,
grid: gs.gridArea,
positioning: { position: 'relative' },
behavior: {
scrollable: true,
collapsible: false,
resizable: true,
draggable: true
},
visibility: {
mobile: true,
tablet: true,
desktop: true
},
padding: '16px',
margin: '0',
borderRadius: '8px',
createdAt: new Date(),
updatedAt: new Date()
}));
// Update layout configuration
const updatedConfig: LayoutConfiguration = {
...this.layoutConfig(),
sections
};
this.layoutConfig.set(updatedConfig);
this.saveLayoutConfiguration();
this.layoutChanged.emit(updatedConfig);
// Exit divide mode
this.exitDivideMode();
this.logger.info('Sections generated from dividers', {
dividerCount: state.horizontal.length + state.vertical.length,
sectionCount: sections.length
});
}
/**
* Cancel dividers
*/
cancelDividers(): void {
this.dividerState.set({
horizontal: [],
vertical: [],
isActive: false,
canConfirm: false
});
this.setCanvasMode('select');
}
/**
* Exit divide mode
*/
private exitDivideMode(): void {
this.dividerState.set({
horizontal: [],
vertical: [],
isActive: false,
canConfirm: false
});
this.setCanvasMode('select');
}
/**
* Get canvas size
*/
private getCanvasSize(): { width: number; height: number } {
if (!this.canvasContainer) {
return { width: 1920, height: 1080 };
}
const el = this.canvasContainer.nativeElement;
return {
width: el.offsetWidth,
height: el.offsetHeight
};
}
}
Template Updates
<!-- grid-canvas.component.html (additions) -->
<!-- Divider Mode Toolbar -->
<div class="divider-toolbar" *ngIf="canvasMode() === 'divide'">
<button (click)="addHorizontalDivider()" class="toolbar-btn">
<span class="icon">➡️</span>
Add Horizontal Line
</button>
<button (click)="addVerticalDivider()" class="toolbar-btn">
<span class="icon">⬇️</span>
Add Vertical Line
</button>
<div class="divider-actions">
<button
(click)="confirmDividers()"
[disabled]="!dividerState().canConfirm"
class="btn-primary"
>
✓ Confirm & Generate Sections
</button>
<button (click)="cancelDividers()" class="btn-secondary">
✗ Cancel
</button>
</div>
</div>
<!-- Divider Lines Overlay -->
<div class="dividers-container" *ngIf="dividerState().isActive">
<lib-divider-tool
*ngFor="let divider of activeDividers()"
[divider]="divider"
[canvasSize]="getCanvasSize()"
(positionChanged)="onDividerPositionChanged($event)"
(onRemove)="removeDivider($event)"
/>
</div>
<!-- Section Preview (during divide mode) -->
<div class="section-preview" *ngIf="dividerState().isActive">
<div
*ngFor="let section of previewSections()"
class="preview-section"
[style.top.px]="section.bounds.top"
[style.left.px]="section.bounds.left"
[style.width.px]="section.bounds.width"
[style.height.px]="section.bounds.height"
>
<span class="section-label">{{ section.name }}</span>
</div>
</div>
💾 STORAGE ARCHITECTURE
Universe-Scoped Storage Schema
// Storage key pattern
`modular-universe/layout/${spaceId}/${universeId}/${moduleType}`
// Example keys
'modular-universe/layout/space-123/universe-456/universe'
'modular-universe/layout/space-123/universe-456/galaxy'
// Stored data
interface StoredLayoutConfiguration extends LayoutConfiguration {
spaceId: string;
universeId: string;
moduleType: 'universe' | 'galaxy';
version: string;
createdAt: Date;
updatedAt: Date;
createdBy: string;
}
Section as Universal Module
// Each section becomes a module
const sectionModule: UniversalModule = {
id: section.id,
name: section.name,
type: 'layout', // or 'custom'
universeId: universeId,
parentId: universeId, // Parent is the universe/galaxy
config: {
...section,
// Section-specific config
},
isActive: true,
createdAt: section.createdAt,
updatedAt: section.updatedAt
};
// Save to storage
await storage.putInUniverse(spaceId, universeId, `module:${section.id}`, sectionModule);
// Register with ModuleRegistry (if reusable)
if (section.isReusable) {
moduleRegistry.registerModule(sectionModule.manifest);
}
Flash Persistence Integration
// Auto-save every change
storage.put(key, data, {
flashPersistence: true, // Instant sync
syncToBackend: true, // Backend backup
debounceMs: 500 // Debounce saves
});
// Cross-tab sync
storage.onChange((event) => {
if (event.key.startsWith('modular-universe/layout/')) {
// Another tab updated layout
this.loadLayoutConfiguration();
}
});
📋 TEMPLATE PRESETS
Common Layout Templates
// services/layout-templates.service.ts
export interface LayoutTemplate {
id: string;
name: string;
description: string;
preview: string; // SVG or image URL
layoutType: 'flexbox' | 'grid';
dividers: {
horizontal: number[]; // Positions
vertical: number[];
};
sections: Partial<SectionConfig>[];
}
export const LAYOUT_TEMPLATES: LayoutTemplate[] = [
{
id: 'header-content-footer',
name: 'Header / Content / Footer',
description: 'Classic 3-section layout',
preview: '/assets/templates/header-content-footer.svg',
layoutType: 'flexbox',
dividers: {
horizontal: [80, -60], // 80px from top, 60px from bottom
vertical: []
},
sections: [
{ name: 'Header', contentType: 'module', flexbox: { flexGrow: 0, flexShrink: 0, flexBasis: '80px' } },
{ name: 'Content', contentType: 'empty', flexbox: { flexGrow: 1, flexShrink: 1, flexBasis: 'auto' } },
{ name: 'Footer', contentType: 'module', flexbox: { flexGrow: 0, flexShrink: 0, flexBasis: '60px' } }
]
},
{
id: 'sidebar-content',
name: 'Sidebar / Content',
description: '2-column layout with sidebar',
preview: '/assets/templates/sidebar-content.svg',
layoutType: 'grid',
dividers: {
horizontal: [],
vertical: [300] // 300px from left
},
sections: [
{ name: 'Sidebar', contentType: 'module', grid: { row: 1, col: 1, rowSpan: 1, colSpan: 1 } },
{ name: 'Content', contentType: 'empty', grid: { row: 1, col: 2, rowSpan: 1, colSpan: 1 } }
]
},
{
id: 'header-sidebar-content-footer',
name: 'Full Dashboard Layout',
description: 'Header + Sidebar + Content + Footer',
preview: '/assets/templates/dashboard.svg',
layoutType: 'grid',
dividers: {
horizontal: [80, -60],
vertical: [250]
},
sections: [
{ name: 'Header', grid: { row: 1, col: 1, rowSpan: 1, colSpan: 2 } },
{ name: 'Sidebar', grid: { row: 2, col: 1, rowSpan: 1, colSpan: 1 } },
{ name: 'Content', grid: { row: 2, col: 2, rowSpan: 1, colSpan: 1 } },
{ name: 'Footer', grid: { row: 3, col: 1, rowSpan: 1, colSpan: 2 } }
]
}
];
@Injectable()
export class LayoutTemplatesService {
/**
* Apply template to canvas
*/
applyTemplate(
template: LayoutTemplate,
canvasSize: { width: number; height: number }
): LayoutConfiguration {
// Convert template to layout configuration
// ...
}
}
🎯 IMPLEMENTATION ROADMAP
Week 1: Divider Foundation
- Create divider.types.ts model
- Create DividerToolComponent
- Create DividerManagerService
- Add divider state to GridCanvasComponent
- Implement add/remove divider methods
Week 2: Cutting Mechanic
- Implement divider dragging
- Implement section generation from dividers
- Add visual feedback (section preview)
- Implement confirm/cancel workflow
- Test with flexbox layout
Week 3: Grid Integration
- Implement CSS Grid generation from dividers
- Test with grid layout
- Add responsive percentage calculation
- Implement layout switching (flexbox ↔ grid)
Week 4: Templates & Polish
- Create layout-templates.service
- Implement template presets
- Add template selector UI
- Polish divider UX (snapping, guides)
- Add keyboard shortcuts
Week 5: Storage & Module Integration
- Implement universe-scoped storage
- Convert sections to universal modules
- Integrate with module-browser
- Test cross-tool workflows
- Documentation
🎯 SUCCESS METRICS
Technical
- Divider dragging: < 16ms (60fps)
- Section generation: < 100ms
- Storage save: < 200ms
- Layout switch: < 100ms
User Experience
- Create layout: < 60 seconds
- Add divider: 1 click + drag
- Generate sections: 1 click
- Assign module to section: < 3 clicks
Platform Integration
- Sections saved as modules
- Sections appear in module-browser
- Layouts persist across sessions
- Flash persistence working
📚 RELATED DOCUMENTS
- Decisions:
DECISIONS-REFRESHED-NOV-2025.md - Foundation Review:
FOUNDATION-REVIEW-NOV-2025.md - Master Plan:
ULTIMATE-PLATFORM-BUILDER-MASTER-PLAN.md
Status: ✅ SPECIFICATION COMPLETE
Next Action: Begin Week 1 implementation
Owner: Development Team
Last Updated: November 14, 2025