mcp-dndgrid 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/PROJECT_SUMMARY.md +482 -0
  2. package/QUICKSTART.md +223 -0
  3. package/README.md +365 -0
  4. package/STATUS.md +315 -0
  5. package/USAGE_GUIDE.md +547 -0
  6. package/dist/chunk-CMGEAPA5.js +157 -0
  7. package/dist/chunk-CMGEAPA5.js.map +1 -0
  8. package/dist/chunk-QZHBI6ZI.js +5281 -0
  9. package/dist/chunk-QZHBI6ZI.js.map +1 -0
  10. package/dist/chunk-SEGVTWSK.js +44 -0
  11. package/dist/chunk-SEGVTWSK.js.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +248012 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/stdio-FWYJXSU7.js +101 -0
  16. package/dist/stdio-FWYJXSU7.js.map +1 -0
  17. package/dist/template-JDMAVVX7.js +9 -0
  18. package/dist/template-JDMAVVX7.js.map +1 -0
  19. package/examples/claude_desktop_config.example.json +12 -0
  20. package/examples/example-complex-editor.tsx +107 -0
  21. package/examples/example-dashboard.tsx +65 -0
  22. package/examples/example-ide-layout.tsx +53 -0
  23. package/examples/test-generator.ts +37 -0
  24. package/examples/test-parser.ts +121 -0
  25. package/examples/test-scenarios.md +496 -0
  26. package/package.json +42 -0
  27. package/src/index.ts +16 -0
  28. package/src/server.ts +314 -0
  29. package/src/tools/analyze-layout.ts +193 -0
  30. package/src/tools/apply-template.ts +125 -0
  31. package/src/tools/generate-layout.ts +235 -0
  32. package/src/tools/interactive-builder.ts +100 -0
  33. package/src/tools/validate-layout.ts +113 -0
  34. package/src/types/layout.ts +48 -0
  35. package/src/types/template.ts +181 -0
  36. package/src/utils/ast-parser.ts +264 -0
  37. package/src/utils/code-generator.ts +123 -0
  38. package/src/utils/layout-analyzer.ts +105 -0
  39. package/src/utils/layout-builder.ts +127 -0
  40. package/src/utils/validator.ts +263 -0
  41. package/stderr.log +1 -0
  42. package/stdout.log +0 -0
  43. package/test-mcp.js +27 -0
  44. package/tsconfig.json +29 -0
  45. package/tsup.config.ts +16 -0
@@ -0,0 +1,235 @@
1
+ import { z } from 'zod';
2
+ import { LayoutBuilder, L } from '../utils/layout-builder.js';
3
+ import { CodeGenerator } from '../utils/code-generator.js';
4
+ import { LayoutAnalyzer } from '../utils/layout-analyzer.js';
5
+ import { Validator } from '../utils/validator.js';
6
+ import type { LayoutNode } from '../types/layout.js';
7
+
8
+ /**
9
+ * Input schema for generate-layout tool
10
+ */
11
+ export const GenerateLayoutInputSchema = z.object({
12
+ description: z
13
+ .string()
14
+ .describe(
15
+ 'Natural language description of desired layout (e.g., "3-panel IDE layout with sidebar, editor, and terminal")'
16
+ ),
17
+ components: z.array(z.string()).describe('List of component names to place in the layout'),
18
+ containerWidth: z.number().optional().default(1200).describe('Container width in pixels'),
19
+ containerHeight: z.number().optional().default(800).describe('Container height in pixels'),
20
+ framework: z
21
+ .enum(['react', 'nextjs-app', 'nextjs-pages'])
22
+ .optional()
23
+ .default('nextjs-app')
24
+ .describe('Target framework (affects "use client" directive)'),
25
+ });
26
+
27
+ export type GenerateLayoutInput = z.infer<typeof GenerateLayoutInputSchema>;
28
+
29
+ /**
30
+ * generate-layout tool implementation
31
+ */
32
+ export async function generateLayout(args: GenerateLayoutInput) {
33
+ const { description, components, containerWidth, containerHeight, framework } = args;
34
+
35
+ // Analyze description to determine layout pattern
36
+ const pattern = analyzeDescription(description, components);
37
+
38
+ // Build layout based on pattern
39
+ let layoutNode: LayoutNode;
40
+
41
+ try {
42
+ layoutNode = buildLayoutFromPattern(pattern, components);
43
+ } catch (error) {
44
+ return {
45
+ content: [
46
+ {
47
+ type: 'text' as const,
48
+ text: `❌ Failed to generate layout: ${error instanceof Error ? error.message : 'Unknown error'}`,
49
+ },
50
+ ],
51
+ };
52
+ }
53
+
54
+ // Create LayoutTree
55
+ const layout = new LayoutBuilder(containerWidth, containerHeight)
56
+ .setRoot(layoutNode)
57
+ .build();
58
+
59
+ // Generate code
60
+ const generator = new CodeGenerator({
61
+ framework,
62
+ width: containerWidth,
63
+ height: containerHeight,
64
+ });
65
+ const code = generator.generate(layout);
66
+
67
+ // Analyze metadata
68
+ const metadata = LayoutAnalyzer.calculateMetadata(layout);
69
+
70
+ // Validate
71
+ const validator = new Validator();
72
+ const validation = validator.validate(layout, false);
73
+
74
+ // Format output
75
+ let output = `# Generated DndGrid Layout\n\n`;
76
+ output += `**Description:** ${description}\n\n`;
77
+
78
+ output += `## Layout Metadata\n\n`;
79
+ output += `- **Items:** ${metadata.itemCount}\n`;
80
+ output += `- **Splits:** ${metadata.splitCount}\n`;
81
+ output += `- **Max Depth:** ${metadata.maxDepth}\n`;
82
+ output += `- **Performance:** ${metadata.estimatedPerformance}\n`;
83
+ output += `- **Pattern:** ${pattern.type}\n\n`;
84
+
85
+ if (validation.warnings.length > 0) {
86
+ output += `## Warnings\n\n`;
87
+ validation.warnings.forEach((warn, i) => {
88
+ output += `${i + 1}. ${warn.message}\n`;
89
+ });
90
+ output += '\n';
91
+ }
92
+
93
+ if (validation.suggestions.length > 0) {
94
+ output += `## Suggestions\n\n`;
95
+ validation.suggestions.forEach((sugg, i) => {
96
+ output += `${i + 1}. ${sugg}\n`;
97
+ });
98
+ output += '\n';
99
+ }
100
+
101
+ output += `## Generated Code\n\n\`\`\`tsx\n${code}\`\`\`\n`;
102
+
103
+ return {
104
+ content: [{ type: 'text' as const, text: output }],
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Layout patterns
110
+ */
111
+ type LayoutPattern =
112
+ | { type: 'ide'; sidebar: string; main: string; bottom: string }
113
+ | { type: 'dashboard'; items: string[] }
114
+ | { type: 'three-column'; left: string; center: string; right: string }
115
+ | { type: 'split'; left: string; right: string; direction: 'horizontal' | 'vertical' }
116
+ | { type: 'custom'; items: string[] };
117
+
118
+ /**
119
+ * Analyze description to determine layout pattern
120
+ */
121
+ function analyzeDescription(description: string, components: string[]): LayoutPattern {
122
+ const lower = description.toLowerCase();
123
+
124
+ // IDE pattern
125
+ if (
126
+ (lower.includes('ide') ||
127
+ (lower.includes('sidebar') && lower.includes('editor')) ||
128
+ lower.includes('3-panel')) &&
129
+ components.length === 3
130
+ ) {
131
+ return {
132
+ type: 'ide',
133
+ sidebar: components[0],
134
+ main: components[1],
135
+ bottom: components[2],
136
+ };
137
+ }
138
+
139
+ // Dashboard pattern (2x2 or grid)
140
+ if (
141
+ (lower.includes('dashboard') || lower.includes('grid') || lower.includes('2x2')) &&
142
+ components.length === 4
143
+ ) {
144
+ return {
145
+ type: 'dashboard',
146
+ items: components,
147
+ };
148
+ }
149
+
150
+ // Three column
151
+ if (
152
+ (lower.includes('three') || lower.includes('3')) &&
153
+ lower.includes('column') &&
154
+ components.length === 3
155
+ ) {
156
+ return {
157
+ type: 'three-column',
158
+ left: components[0],
159
+ center: components[1],
160
+ right: components[2],
161
+ };
162
+ }
163
+
164
+ // Split view
165
+ if (components.length === 2 && (lower.includes('split') || lower.includes('two'))) {
166
+ const direction = lower.includes('horizontal') || lower.includes('top')
167
+ ? 'horizontal'
168
+ : 'vertical';
169
+ return {
170
+ type: 'split',
171
+ left: components[0],
172
+ right: components[1],
173
+ direction,
174
+ };
175
+ }
176
+
177
+ // Custom/fallback
178
+ return {
179
+ type: 'custom',
180
+ items: components,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Build layout node from pattern
186
+ */
187
+ function buildLayoutFromPattern(pattern: LayoutPattern, components: string[]): LayoutNode {
188
+ switch (pattern.type) {
189
+ case 'ide':
190
+ return L.v(
191
+ 0.2,
192
+ L.item(pattern.sidebar),
193
+ L.h(0.7, L.item(pattern.main), L.item(pattern.bottom))
194
+ );
195
+
196
+ case 'dashboard':
197
+ return L.h(
198
+ 0.5,
199
+ L.v(0.5, L.item(pattern.items[0]), L.item(pattern.items[1])),
200
+ L.v(0.5, L.item(pattern.items[2]), L.item(pattern.items[3]))
201
+ );
202
+
203
+ case 'three-column':
204
+ return L.v(
205
+ 0.2,
206
+ L.item(pattern.left),
207
+ L.v(0.75, L.item(pattern.center), L.item(pattern.right))
208
+ );
209
+
210
+ case 'split':
211
+ if (pattern.direction === 'horizontal') {
212
+ return L.h(0.5, L.item(pattern.left), L.item(pattern.right));
213
+ } else {
214
+ return L.v(0.5, L.item(pattern.left), L.item(pattern.right));
215
+ }
216
+
217
+ case 'custom':
218
+ // Simple fallback: vertical splits for all components
219
+ if (components.length === 1) {
220
+ return L.item(components[0]);
221
+ }
222
+ if (components.length === 2) {
223
+ return L.v(0.5, L.item(components[0]), L.item(components[1]));
224
+ }
225
+ // For 3+ components, create nested vertical splits
226
+ let node: LayoutNode = L.item(components[components.length - 1]);
227
+ for (let i = components.length - 2; i >= 0; i--) {
228
+ node = L.v(1 / (i + 2), L.item(components[i]), node);
229
+ }
230
+ return node;
231
+
232
+ default:
233
+ throw new Error(`Unknown pattern type: ${(pattern as any).type}`);
234
+ }
235
+ }
@@ -0,0 +1,100 @@
1
+ import { z } from 'zod';
2
+ import { BUILTIN_TEMPLATES } from '../types/template.js';
3
+
4
+ /**
5
+ * Input schema for interactive-builder tool
6
+ */
7
+ export const InteractiveBuilderInputSchema = z.object({
8
+ action: z
9
+ .enum(['list-templates', 'select-template', 'help'])
10
+ .describe('Action to perform'),
11
+ templateName: z.string().optional().describe('Template name (for select-template action)'),
12
+ });
13
+
14
+ export type InteractiveBuilderInput = z.infer<typeof InteractiveBuilderInputSchema>;
15
+
16
+ /**
17
+ * interactive-builder tool implementation
18
+ *
19
+ * Simplified version that guides users through template selection
20
+ */
21
+ export async function interactiveBuilder(args: InteractiveBuilderInput) {
22
+ const { action, templateName } = args;
23
+
24
+ let output = '';
25
+
26
+ switch (action) {
27
+ case 'list-templates':
28
+ output = `# Available Templates\n\n`;
29
+ Object.entries(BUILTIN_TEMPLATES).forEach(([key, template]) => {
30
+ output += `## ${template.name} (\`${key}\`)\n\n`;
31
+ output += `${template.description}\n\n`;
32
+ output += `**Preview:**\n\`\`\`\n${template.preview}\n\`\`\`\n\n`;
33
+ output += `**Required Components:**\n`;
34
+ template.slots.forEach((slot) => {
35
+ output += `- \`${slot}\`\n`;
36
+ });
37
+ output += `\n---\n\n`;
38
+ });
39
+ output += `## Next Steps\n\n`;
40
+ output += `1. Choose a template that matches your needs\n`;
41
+ output += `2. Use the \`apply-template\` tool with your component names\n`;
42
+ output += `\nExample:\n\`\`\`\napply-template --templateName ide-layout --components '{"sidebar": "Sidebar", "editor": "CodeEditor", "terminal": "Terminal"}'\n\`\`\`\n`;
43
+ break;
44
+
45
+ case 'select-template':
46
+ if (!templateName) {
47
+ output = `❌ Please provide a template name.\n\nUse action="list-templates" to see available templates.`;
48
+ break;
49
+ }
50
+
51
+ const template = BUILTIN_TEMPLATES[templateName];
52
+ if (!template) {
53
+ output = `❌ Template "${templateName}" not found.\n\nAvailable: ${Object.keys(BUILTIN_TEMPLATES).join(', ')}`;
54
+ break;
55
+ }
56
+
57
+ output = `# Selected Template: ${template.name}\n\n`;
58
+ output += `${template.description}\n\n`;
59
+ output += `**Preview:**\n\`\`\`\n${template.preview}\n\`\`\`\n\n`;
60
+ output += `## Required Components\n\n`;
61
+ output += `You need to provide ${template.slots.length} components:\n\n`;
62
+ template.slots.forEach((slot, i) => {
63
+ output += `${i + 1}. **${slot}**: Your component name here\n`;
64
+ });
65
+ output += `\n## Apply This Template\n\n`;
66
+ output += `Use the \`apply-template\` tool:\n\n`;
67
+ output += `\`\`\`\n`;
68
+ output += `templateName: "${templateName}"\n`;
69
+ output += `components:\n`;
70
+ template.slots.forEach((slot) => {
71
+ output += ` ${slot}: "YourComponentName"\n`;
72
+ });
73
+ output += `\`\`\`\n`;
74
+ break;
75
+
76
+ case 'help':
77
+ output = `# Interactive Layout Builder - Help\n\n`;
78
+ output += `This tool helps you build DndGrid layouts interactively.\n\n`;
79
+ output += `## Available Actions\n\n`;
80
+ output += `1. **list-templates** - Show all available templates\n`;
81
+ output += `2. **select-template** - Get details about a specific template\n`;
82
+ output += `3. **help** - Show this help message\n\n`;
83
+ output += `## Workflow\n\n`;
84
+ output += `1. List templates to see what's available\n`;
85
+ output += `2. Select a template that fits your needs\n`;
86
+ output += `3. Use \`apply-template\` with your component names\n\n`;
87
+ output += `## Other Tools\n\n`;
88
+ output += `- **generate-layout** - Generate from natural language description\n`;
89
+ output += `- **validate-layout** - Validate existing code\n`;
90
+ output += `- **analyze-layout** - Analyze existing layout\n`;
91
+ break;
92
+
93
+ default:
94
+ output = `❌ Unknown action: ${action}`;
95
+ }
96
+
97
+ return {
98
+ content: [{ type: 'text' as const, text: output }],
99
+ };
100
+ }
@@ -0,0 +1,113 @@
1
+ import { z } from 'zod';
2
+ import { Validator } from '../utils/validator.js';
3
+ import { ASTParser } from '../utils/ast-parser.js';
4
+
5
+ /**
6
+ * Input schema for validate-layout tool
7
+ */
8
+ export const ValidateLayoutInputSchema = z.object({
9
+ code: z.string().describe('DndGrid code to validate'),
10
+ strict: z.boolean().optional().default(false).describe('Enable strict validation (includes performance checks)'),
11
+ });
12
+
13
+ export type ValidateLayoutInput = z.infer<typeof ValidateLayoutInputSchema>;
14
+
15
+ /**
16
+ * validate-layout tool implementation
17
+ */
18
+ export async function validateLayout(args: ValidateLayoutInput) {
19
+ const { code, strict } = args;
20
+
21
+ // First, validate code syntax
22
+ const validator = new Validator();
23
+ const codeValidation = validator.validateCode(code, strict);
24
+
25
+ // Try to parse the code to get layout tree
26
+ const parser = new ASTParser();
27
+ const layout = parser.parse(code);
28
+
29
+ let treeValidation = null;
30
+ if (layout) {
31
+ treeValidation = validator.validate(layout, strict);
32
+ }
33
+
34
+ // Combine results
35
+ const allErrors = [...codeValidation.errors, ...(treeValidation?.errors || [])];
36
+ const allWarnings = [...codeValidation.warnings, ...(treeValidation?.warnings || [])];
37
+ const allSuggestions = [...codeValidation.suggestions, ...(treeValidation?.suggestions || [])];
38
+
39
+ const valid = allErrors.length === 0;
40
+
41
+ // Format output
42
+ let output = `# DndGrid Layout Validation\n\n`;
43
+
44
+ output += `**Status:** ${valid ? '✅ Valid' : '❌ Invalid'}\n\n`;
45
+
46
+ if (allErrors.length > 0) {
47
+ output += `## Errors (${allErrors.length})\n\n`;
48
+ allErrors.forEach((err, i) => {
49
+ output += `${i + 1}. **[${err.type}]** ${err.message}\n`;
50
+ if (err.fix) {
51
+ output += ` - Fix: \`${err.fix}\`\n`;
52
+ }
53
+ if (err.line) {
54
+ output += ` - Line: ${err.line}\n`;
55
+ }
56
+ output += '\n';
57
+ });
58
+ }
59
+
60
+ if (allWarnings.length > 0) {
61
+ output += `## Warnings (${allWarnings.length})\n\n`;
62
+ allWarnings.forEach((warn, i) => {
63
+ output += `${i + 1}. ${warn.message}\n`;
64
+ if (warn.suggestion) {
65
+ output += ` - Suggestion: ${warn.suggestion}\n`;
66
+ }
67
+ output += '\n';
68
+ });
69
+ }
70
+
71
+ if (allSuggestions.length > 0) {
72
+ output += `## Suggestions\n\n`;
73
+ allSuggestions.forEach((sugg, i) => {
74
+ output += `${i + 1}. ${sugg}\n`;
75
+ });
76
+ output += '\n';
77
+ }
78
+
79
+ if (layout) {
80
+ output += `## Layout Structure\n\n`;
81
+ output += `- Items: ${countItems(layout.child)}\n`;
82
+ output += `- Splits: ${countSplits(layout.child)}\n`;
83
+ output += `- Max Depth: ${getMaxDepth(layout.child)}\n`;
84
+ }
85
+
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text' as const,
90
+ text: output,
91
+ },
92
+ ],
93
+ };
94
+ }
95
+
96
+ // Helper functions
97
+ function countItems(node: any): number {
98
+ if (node.type === 'item') return 1;
99
+ return countItems(node.primary) + countItems(node.secondary);
100
+ }
101
+
102
+ function countSplits(node: any): number {
103
+ if (node.type === 'item') return 0;
104
+ return 1 + countSplits(node.primary) + countSplits(node.secondary);
105
+ }
106
+
107
+ function getMaxDepth(node: any, depth: number = 1): number {
108
+ if (node.type === 'item') return depth;
109
+ return Math.max(
110
+ getMaxDepth(node.primary, depth + 1),
111
+ getMaxDepth(node.secondary, depth + 1)
112
+ );
113
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Layout type definitions for DndGrid MCP server
3
+ */
4
+
5
+ export type DndSplitDirection = 'horizontal' | 'vertical';
6
+
7
+ export interface LayoutItem {
8
+ type: 'item';
9
+ component: string;
10
+ }
11
+
12
+ export interface LayoutSplit {
13
+ type: 'split';
14
+ direction: DndSplitDirection;
15
+ ratio: number; // 0 < ratio < 1
16
+ primary: LayoutNode;
17
+ secondary: LayoutNode;
18
+ }
19
+
20
+ export type LayoutNode = LayoutItem | LayoutSplit;
21
+
22
+ export interface LayoutTree {
23
+ type: 'container';
24
+ width: number;
25
+ height: number;
26
+ child: LayoutNode;
27
+ }
28
+
29
+ export interface LayoutMetadata {
30
+ splitCount: number;
31
+ itemCount: number;
32
+ maxDepth: number;
33
+ estimatedPerformance: 'excellent' | 'good' | 'fair' | 'poor';
34
+ }
35
+
36
+ export interface ValidationError {
37
+ type: 'syntax' | 'structure' | 'constraint';
38
+ message: string;
39
+ line?: number;
40
+ column?: number;
41
+ fix?: string;
42
+ }
43
+
44
+ export interface ValidationWarning {
45
+ message: string;
46
+ line?: number;
47
+ suggestion?: string;
48
+ }
@@ -0,0 +1,181 @@
1
+ import type { LayoutNode } from './layout.js';
2
+
3
+ /**
4
+ * Template node with slots for components
5
+ */
6
+ export interface TemplateNode {
7
+ type: 'split' | 'item';
8
+ direction?: 'horizontal' | 'vertical';
9
+ ratio?: number;
10
+ slot?: string; // For item nodes
11
+ primary?: TemplateNode;
12
+ secondary?: TemplateNode;
13
+ }
14
+
15
+ /**
16
+ * Layout template definition
17
+ */
18
+ export interface LayoutTemplate {
19
+ name: string;
20
+ description: string;
21
+ preview: string; // ASCII art preview
22
+ slots: string[]; // Required component slots
23
+ defaultRatios: Record<string, number>;
24
+ tree: TemplateNode;
25
+ }
26
+
27
+ /**
28
+ * Built-in templates
29
+ */
30
+ export const BUILTIN_TEMPLATES: Record<string, LayoutTemplate> = {
31
+ 'ide-layout': {
32
+ name: 'IDE Layout',
33
+ description: '3-panel layout: sidebar (20%), editor (56%), terminal (24%)',
34
+ preview: `┌────┬────────────┐
35
+ │ │ │
36
+ │ S │ Editor │
37
+ │ I │ │
38
+ │ D ├────────────┤
39
+ │ E │ Terminal │
40
+ └────┴────────────┘`,
41
+ slots: ['sidebar', 'editor', 'terminal'],
42
+ defaultRatios: {
43
+ 'vertical-main': 0.2,
44
+ 'horizontal-content': 0.7,
45
+ },
46
+ tree: {
47
+ type: 'split',
48
+ direction: 'vertical',
49
+ ratio: 0.2,
50
+ primary: {
51
+ type: 'item',
52
+ slot: 'sidebar',
53
+ },
54
+ secondary: {
55
+ type: 'split',
56
+ direction: 'horizontal',
57
+ ratio: 0.7,
58
+ primary: {
59
+ type: 'item',
60
+ slot: 'editor',
61
+ },
62
+ secondary: {
63
+ type: 'item',
64
+ slot: 'terminal',
65
+ },
66
+ },
67
+ },
68
+ },
69
+
70
+ 'dashboard-2x2': {
71
+ name: 'Dashboard 2x2',
72
+ description: '2x2 grid layout for dashboard widgets',
73
+ preview: `┌──────┬──────┐
74
+ │ W1 │ W2 │
75
+ ├──────┼──────┤
76
+ │ W3 │ W4 │
77
+ └──────┴──────┘`,
78
+ slots: ['widget1', 'widget2', 'widget3', 'widget4'],
79
+ defaultRatios: {
80
+ 'horizontal-top': 0.5,
81
+ 'horizontal-bottom': 0.5,
82
+ 'vertical-left': 0.5,
83
+ 'vertical-right': 0.5,
84
+ },
85
+ tree: {
86
+ type: 'split',
87
+ direction: 'horizontal',
88
+ ratio: 0.5,
89
+ primary: {
90
+ type: 'split',
91
+ direction: 'vertical',
92
+ ratio: 0.5,
93
+ primary: {
94
+ type: 'item',
95
+ slot: 'widget1',
96
+ },
97
+ secondary: {
98
+ type: 'item',
99
+ slot: 'widget2',
100
+ },
101
+ },
102
+ secondary: {
103
+ type: 'split',
104
+ direction: 'vertical',
105
+ ratio: 0.5,
106
+ primary: {
107
+ type: 'item',
108
+ slot: 'widget3',
109
+ },
110
+ secondary: {
111
+ type: 'item',
112
+ slot: 'widget4',
113
+ },
114
+ },
115
+ },
116
+ },
117
+
118
+ 'three-column': {
119
+ name: 'Three Column',
120
+ description: '3-column layout (20% / 60% / 20%)',
121
+ preview: `┌───┬────────┬───┐
122
+ │ │ │ │
123
+ │ L │ Center │ R │
124
+ │ │ │ │
125
+ └───┴────────┴───┘`,
126
+ slots: ['left', 'center', 'right'],
127
+ defaultRatios: {
128
+ 'vertical-left': 0.2,
129
+ 'vertical-right': 0.75,
130
+ },
131
+ tree: {
132
+ type: 'split',
133
+ direction: 'vertical',
134
+ ratio: 0.2,
135
+ primary: {
136
+ type: 'item',
137
+ slot: 'left',
138
+ },
139
+ secondary: {
140
+ type: 'split',
141
+ direction: 'vertical',
142
+ ratio: 0.75,
143
+ primary: {
144
+ type: 'item',
145
+ slot: 'center',
146
+ },
147
+ secondary: {
148
+ type: 'item',
149
+ slot: 'right',
150
+ },
151
+ },
152
+ },
153
+ },
154
+
155
+ 'split-view': {
156
+ name: 'Split View',
157
+ description: 'Simple 50/50 split (horizontal or vertical)',
158
+ preview: `┌──────────┬──────────┐
159
+ │ │ │
160
+ │ Left │ Right │
161
+ │ │ │
162
+ └──────────┴──────────┘`,
163
+ slots: ['left', 'right'],
164
+ defaultRatios: {
165
+ 'vertical-main': 0.5,
166
+ },
167
+ tree: {
168
+ type: 'split',
169
+ direction: 'vertical',
170
+ ratio: 0.5,
171
+ primary: {
172
+ type: 'item',
173
+ slot: 'left',
174
+ },
175
+ secondary: {
176
+ type: 'item',
177
+ slot: 'right',
178
+ },
179
+ },
180
+ },
181
+ };