@symbo.ls/mcp 1.0.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.
- package/.env.example +16 -0
- package/.env.railway +13 -0
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/mcp.json +57 -0
- package/package.json +20 -0
- package/pyproject.toml +25 -0
- package/railway.toml +26 -0
- package/run.sh +17 -0
- package/symbols_mcp/__init__.py +1 -0
- package/symbols_mcp/server.py +1114 -0
- package/symbols_mcp/skills/ACCESSIBILITY.md +471 -0
- package/symbols_mcp/skills/ACCESSIBILITY_AUDITORY.md +70 -0
- package/symbols_mcp/skills/AGENT_INSTRUCTIONS.md +257 -0
- package/symbols_mcp/skills/BRAND_INDENTITY.md +69 -0
- package/symbols_mcp/skills/BUILT_IN_COMPONENTS.md +304 -0
- package/symbols_mcp/skills/CLAUDE.md +2158 -0
- package/symbols_mcp/skills/CLI_QUICK_START.md +205 -0
- package/symbols_mcp/skills/DESIGN_CRITIQUE.md +64 -0
- package/symbols_mcp/skills/DESIGN_DIRECTION.md +320 -0
- package/symbols_mcp/skills/DESIGN_SYSTEM_ARCHITECT.md +64 -0
- package/symbols_mcp/skills/DESIGN_SYSTEM_CONFIG.md +487 -0
- package/symbols_mcp/skills/DESIGN_SYSTEM_IN_PROPS.md +136 -0
- package/symbols_mcp/skills/DESIGN_TO_CODE.md +64 -0
- package/symbols_mcp/skills/DESIGN_TREND.md +50 -0
- package/symbols_mcp/skills/DOMQL_v2-v3_MIGRATION.md +236 -0
- package/symbols_mcp/skills/FIGMA_MATCHING.md +63 -0
- package/symbols_mcp/skills/GARY_TAN.md +80 -0
- package/symbols_mcp/skills/MARKETING_ASSETS.md +66 -0
- package/symbols_mcp/skills/MIGRATE_TO_SYMBOLS.md +614 -0
- package/symbols_mcp/skills/QUICKSTART.md +79 -0
- package/symbols_mcp/skills/SYMBOLS_LOCAL_INSTRUCTIONS.md +1405 -0
- package/symbols_mcp/skills/THE_PRESENTATION.md +69 -0
- package/symbols_mcp/skills/UI_UX_PATTERNS.md +68 -0
- package/windsurf-mcp-config.json +18 -0
|
@@ -0,0 +1,1405 @@
|
|
|
1
|
+
# DOMQL/Symbols Project Structure - AI Instructions
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This is a strict, environment-agnostic folder structure designed for the Symbols platform. It generates independent files without JavaScript module preloading, enabling seamless rendering across all environments: VSCode, file structure, Symbols platform, server rendering, and web browsers.
|
|
6
|
+
|
|
7
|
+
## Core Principle
|
|
8
|
+
|
|
9
|
+
**NO JavaScript imports/exports for component usage.** Components are registered once in their respective folders and reused through a declarative configuration tree using object notation.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Folder Structure & Export Patterns
|
|
14
|
+
|
|
15
|
+
### 1. **Components** (`/smbls/components/`)
|
|
16
|
+
|
|
17
|
+
**File Pattern:**
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
// components/Header.js
|
|
21
|
+
export const Header = {
|
|
22
|
+
extends: "Flex", // Base component type
|
|
23
|
+
minWidth: "G2",
|
|
24
|
+
padding: "A",
|
|
25
|
+
// Props and styling applied directly - no 'props' wrapper
|
|
26
|
+
|
|
27
|
+
// Nested children as properties
|
|
28
|
+
Search: {
|
|
29
|
+
extends: "Input",
|
|
30
|
+
flex: 1,
|
|
31
|
+
},
|
|
32
|
+
Avatar: {
|
|
33
|
+
extends: "Image",
|
|
34
|
+
boxSize: "B",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Key Rules:**
|
|
40
|
+
|
|
41
|
+
- Each component is a named export: `export const ComponentName = { ... }`
|
|
42
|
+
- Component name MUST match filename (PascalCase)
|
|
43
|
+
- Contains declarative object structure with nested child components
|
|
44
|
+
- Can reference other components by name in the tree without imports
|
|
45
|
+
- Props use design system tokens (e.g., `minWidth: 'G2'`, `padding: 'A'`)
|
|
46
|
+
|
|
47
|
+
**Usage in Components Tree:**
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
{
|
|
51
|
+
Header: {}, // No import needed, referenced by name
|
|
52
|
+
Content: {
|
|
53
|
+
Article: {},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### 2. **Pages** (`/smbls/pages/`)
|
|
61
|
+
|
|
62
|
+
**File Pattern:**
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
// pages/add-network.js (dash-case filename)
|
|
66
|
+
export const addNetwork = {
|
|
67
|
+
extends: "Page", // camelCase export name
|
|
68
|
+
width: "100%",
|
|
69
|
+
padding: "A",
|
|
70
|
+
|
|
71
|
+
// Event handlers applied directly to properties
|
|
72
|
+
onRender: async (el, state) => {
|
|
73
|
+
await el.call("auth");
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
Form: {
|
|
77
|
+
extends: "Box",
|
|
78
|
+
Input: {
|
|
79
|
+
extends: "Input",
|
|
80
|
+
placeholder: "Network name",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Key Rules:**
|
|
87
|
+
|
|
88
|
+
- Filenames use dash-case (kebab-case): `add-network.js`, `edit-node.js`, `dashboard.js`
|
|
89
|
+
- Exports use camelCase: `export const addNetwork = { ... }`, `export const editNode = { ... }`
|
|
90
|
+
- Pages extend from 'Page' component
|
|
91
|
+
- Contain complete page structure with nested components
|
|
92
|
+
- Can call functions using `el.call('functionName')`
|
|
93
|
+
|
|
94
|
+
**Usage:**
|
|
95
|
+
Pages are registered in [pages/index.js](smbls/pages/index.js) as a default export object mapping routes to pages.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 3. **Functions** (`/smbls/functions/`)
|
|
100
|
+
|
|
101
|
+
**File Pattern:**
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
// functions/parseNetworkRow.js
|
|
105
|
+
export const parseNetworkRow = function parseNetworkRow(data) {
|
|
106
|
+
// Function logic
|
|
107
|
+
return processedData;
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Key Rules:**
|
|
112
|
+
|
|
113
|
+
- Each function is a named export matching filename (camelCase)
|
|
114
|
+
- Functions are pure, standalone utilities
|
|
115
|
+
- NO imports of other functions; reuse only through composition
|
|
116
|
+
|
|
117
|
+
**Usage in Components:**
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
{
|
|
121
|
+
Button: {
|
|
122
|
+
onClick: (el) => el.call("parseNetworkRow", data);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Functions are called via `element.call('functionName', ...args)` from within component handlers.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 4. **Methods** (`/smbls/methods/`)
|
|
132
|
+
|
|
133
|
+
**File Pattern:**
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
// methods/formatDate.js
|
|
137
|
+
export const formatDate = function (date) {
|
|
138
|
+
return new Intl.DateTimeFormat().format(date);
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Key Rules:**
|
|
143
|
+
|
|
144
|
+
- Utility methods that extend element behavior
|
|
145
|
+
- Registered once, reused across all components
|
|
146
|
+
|
|
147
|
+
**Usage in Components:**
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
{
|
|
151
|
+
Button: {
|
|
152
|
+
onClick: (el) => el.methodName(args);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Methods are called directly on element instances: `element.methodName()`
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### 5. **Design System** (`/smbls/designSystem/`)
|
|
162
|
+
|
|
163
|
+
**Structure:**
|
|
164
|
+
|
|
165
|
+
Flat folder with individual files for each design aspect:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
designSystem/
|
|
169
|
+
├── index.js # Registry with namespaces
|
|
170
|
+
├── color.js # Color tokens
|
|
171
|
+
├── spacing.js # Spacing/sizing scale
|
|
172
|
+
├── typography.js # Typography definitions
|
|
173
|
+
├── grid.js # Grid system
|
|
174
|
+
├── theme.js # Theme definitions
|
|
175
|
+
├── font.js # Font definitions
|
|
176
|
+
├── icons.js # SVG icons (flat)
|
|
177
|
+
├── animation.js # Animation definitions
|
|
178
|
+
├── timing.js # Timing/duration values
|
|
179
|
+
└── ... # Other design tokens
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**File Pattern:**
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// designSystem/color.js
|
|
186
|
+
export default {
|
|
187
|
+
black: '#000',
|
|
188
|
+
white: '#fff',
|
|
189
|
+
primary: '#0066cc',
|
|
190
|
+
secondary: '#666666',
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// designSystem/spacing.js
|
|
194
|
+
export default {
|
|
195
|
+
base: 16,
|
|
196
|
+
ratio: 1.618,
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Key Rules:**
|
|
201
|
+
|
|
202
|
+
- Each file exports a single design aspect as default object
|
|
203
|
+
- Organized by design concern: color, spacing, typography, grid, etc.
|
|
204
|
+
- Used in component properties through shorthand (e.g., `padding: 'A'`, `color: 'primary'`)
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### 6. **State** (`/smbls/state/`)
|
|
209
|
+
|
|
210
|
+
**Structure:**
|
|
211
|
+
|
|
212
|
+
Flat folder for state and data files:
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
state/
|
|
216
|
+
├── index.js # Registry exporting all state
|
|
217
|
+
├── metrics.js # Metrics data
|
|
218
|
+
└── ... # Other state files
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**File Pattern:**
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
// state/metrics.js
|
|
225
|
+
export default [
|
|
226
|
+
{
|
|
227
|
+
title: "Status",
|
|
228
|
+
items: [{ caption: "Live", value: 14 }],
|
|
229
|
+
},
|
|
230
|
+
// ...
|
|
231
|
+
];
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**state/index.js:**
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
export default {
|
|
238
|
+
metrics: [],
|
|
239
|
+
user: {},
|
|
240
|
+
// ... initial state values inline — no imports
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
> ⚠️ Do NOT import from other state files. Define all initial state inline in a single `state.js` or `state/index.js`.
|
|
245
|
+
|
|
246
|
+
**Key Rules:**
|
|
247
|
+
|
|
248
|
+
- State organized in folder with separate files by concern
|
|
249
|
+
- Each file exports default object with related state
|
|
250
|
+
- No logic or methods - only data structures
|
|
251
|
+
- Used as `state` parameter in component event handlers
|
|
252
|
+
- Accessed and modified through: `state.propertyName`
|
|
253
|
+
|
|
254
|
+
**Usage in Components:**
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
{
|
|
258
|
+
Button: {
|
|
259
|
+
onClick: (el, state) => {
|
|
260
|
+
state.user.authenticated = true;
|
|
261
|
+
console.log(state.metrics.status);
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### 7. **Variables** (`/smbls/vars.js`)
|
|
270
|
+
|
|
271
|
+
**File Pattern:**
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
// vars.js
|
|
275
|
+
export default {
|
|
276
|
+
// Global constants and settings
|
|
277
|
+
APP_VERSION: "1.0.0",
|
|
278
|
+
API_BASE_URL: "https://api.example.com",
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Key Rules:**
|
|
283
|
+
|
|
284
|
+
- Global constants and application-wide settings
|
|
285
|
+
- Read-only values
|
|
286
|
+
- Reference in components: `onClick: (el, state) => { const url = state.root.vars.API_BASE_URL; }`
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### 8. **Config** (`/smbls/config.js`)
|
|
291
|
+
|
|
292
|
+
**File Pattern:**
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// config.js
|
|
296
|
+
export default {
|
|
297
|
+
useReset: true,
|
|
298
|
+
useVariable: true,
|
|
299
|
+
useFontImport: true,
|
|
300
|
+
useIconSprite: true,
|
|
301
|
+
useSvgSprite: true,
|
|
302
|
+
useDefaultConfig: true,
|
|
303
|
+
useDocumentTheme: true,
|
|
304
|
+
verbose: false,
|
|
305
|
+
};
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Key Rules:**
|
|
309
|
+
|
|
310
|
+
- Single default export with platform/application configuration
|
|
311
|
+
- Boolean flags for feature toggles
|
|
312
|
+
- Settings that control runtime behavior
|
|
313
|
+
- Used internally by the DOMQL runtime
|
|
314
|
+
- Affects how components are rendered and styled
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### 9. **Dependencies** (`/smbls/dependencies.js`)
|
|
319
|
+
|
|
320
|
+
**File Pattern:**
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
// dependencies.js - Fixed version numbers
|
|
324
|
+
export default {
|
|
325
|
+
"chart.js": "4.4.9",
|
|
326
|
+
"fuse.js": "7.1.0",
|
|
327
|
+
lit: "3.1.0",
|
|
328
|
+
"ninja-keys": "1.2.2",
|
|
329
|
+
};
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Key Rules:**
|
|
333
|
+
|
|
334
|
+
- Maps external npm package names to **fixed version numbers**
|
|
335
|
+
- **NO module preloading** - packages defined here, imported on-demand
|
|
336
|
+
- Dynamic imports only within event handlers and functions via `await import()`
|
|
337
|
+
- Keeps structure resolvable without build step or package installation
|
|
338
|
+
- Version numbers must be exact and locked (no ranges like `^` or `~`)
|
|
339
|
+
|
|
340
|
+
**Dynamic Import Pattern:**
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
{
|
|
344
|
+
Button: {
|
|
345
|
+
onClick: async (element, state) => {
|
|
346
|
+
// Import at runtime when needed
|
|
347
|
+
const { Chart } = await import("chart.js");
|
|
348
|
+
const chart = new Chart(element, {
|
|
349
|
+
/* config */
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
This approach ensures:
|
|
357
|
+
|
|
358
|
+
- No top-level imports clutter the file
|
|
359
|
+
- Packages loaded only when actually used
|
|
360
|
+
- Works in any environment (browser, server, file system)
|
|
361
|
+
- Declarative tree remains environment-agnostic
|
|
362
|
+
- Version consistency across environments
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
### 10. **Files** (`/smbls/files/`)
|
|
367
|
+
|
|
368
|
+
**File Pattern:**
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// files/index.js - Asset manifest managed via Symbols SDK
|
|
372
|
+
// DO NOT manually edit this file
|
|
373
|
+
// To add files: Use Symbols SDK to upload, then place response data here
|
|
374
|
+
|
|
375
|
+
export default {
|
|
376
|
+
// ... file data managed by SDK
|
|
377
|
+
};
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Key Rules:**
|
|
381
|
+
|
|
382
|
+
- Single default export mapping file keys to file metadata
|
|
383
|
+
- **Managed entirely via Symbols SDK** - do NOT manually create entries
|
|
384
|
+
- Use strict folder structure
|
|
385
|
+
|
|
386
|
+
**Adding Files:**
|
|
387
|
+
|
|
388
|
+
When the AI adds assets or files, it should use Symbols SDK commands to upload files and return related data to place in `files/`.
|
|
389
|
+
|
|
390
|
+
**Usage in Components:**
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
{
|
|
394
|
+
Image: {
|
|
395
|
+
src: 'Arbitrum.png',
|
|
396
|
+
// or rare occasions
|
|
397
|
+
src: (element, state, context) => context.files['Arbitrum.png'].content.src,
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
### 11. **Fonts** (`/smbls/designSystem/fonts.js`)
|
|
405
|
+
|
|
406
|
+
**File Pattern:**
|
|
407
|
+
|
|
408
|
+
```javascript
|
|
409
|
+
// designSystem/fonts.js - Fonts managed via Symbols SDK
|
|
410
|
+
// To add fonts: Upload via SDK, place response in this file
|
|
411
|
+
|
|
412
|
+
export default {
|
|
413
|
+
// ... font data managed by SDK
|
|
414
|
+
};
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Key Rules:**
|
|
418
|
+
|
|
419
|
+
- Single default export with font definitions
|
|
420
|
+
- **Managed via Symbols SDK file upload** - don't add manually
|
|
421
|
+
|
|
422
|
+
**Adding Fonts:**
|
|
423
|
+
|
|
424
|
+
When adding fonts, use the SDK to upload and place the returned metadata in `designSystem/fonts.js`.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
### 12. **Icons** (`/smbls/designSystem/icons.js`)
|
|
429
|
+
|
|
430
|
+
**File Pattern:**
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
// designSystem/icons.js - Flat SVG icon collection
|
|
434
|
+
export default {
|
|
435
|
+
logo: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7L12 12L22 7L12 2Z"/><path d="M2 17L12 22L22 17"/><path d="M2 12L12 17L22 12"/></svg>',
|
|
436
|
+
|
|
437
|
+
chevronLeft:
|
|
438
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>',
|
|
439
|
+
|
|
440
|
+
chevronRight:
|
|
441
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>',
|
|
442
|
+
|
|
443
|
+
search:
|
|
444
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>',
|
|
445
|
+
|
|
446
|
+
menu: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/></svg>',
|
|
447
|
+
|
|
448
|
+
close:
|
|
449
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
|
|
450
|
+
};
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Key Rules:**
|
|
454
|
+
|
|
455
|
+
- **Completely flat structure** - no nesting or categorization
|
|
456
|
+
- Each icon is a named property with inline SVG string
|
|
457
|
+
- Icon names in camelCase: `chevronLeft`, `arrowUp`, `checkmark`
|
|
458
|
+
- SVG uses `stroke="currentColor"` or `fill="currentColor"` for styling
|
|
459
|
+
- No folder structure or category prefixes
|
|
460
|
+
|
|
461
|
+
**Usage in Components:**
|
|
462
|
+
|
|
463
|
+
```javascript
|
|
464
|
+
{
|
|
465
|
+
Icon: {
|
|
466
|
+
name: 'chevronLeft',
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
Button: {
|
|
470
|
+
extends: 'Button',
|
|
471
|
+
icon: 'search',
|
|
472
|
+
},
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### 13. **Snippets** (`/smbls/snippets/`)
|
|
479
|
+
|
|
480
|
+
**File Pattern:**
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
// snippets/index.js
|
|
484
|
+
export * from "./oneSnippet.js";
|
|
485
|
+
export * from "./dataSnippet.js";
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Key Rules:**
|
|
489
|
+
|
|
490
|
+
- Reusable component or layout snippets
|
|
491
|
+
- Named exports of common patterns
|
|
492
|
+
- Used to reduce repetition of complex structures
|
|
493
|
+
- Can contain random snippets, JSON data, classes, functions, or any other reusable code
|
|
494
|
+
- **NOT** a component definition itself, but can be used within one
|
|
495
|
+
|
|
496
|
+
**Example Snippet:**
|
|
497
|
+
|
|
498
|
+
```javascript
|
|
499
|
+
// snippets/dataMockSnippet.js
|
|
500
|
+
export const dataMockSnippet = {
|
|
501
|
+
data: [],
|
|
502
|
+
};
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Usage in Components:**
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
{
|
|
509
|
+
List: {
|
|
510
|
+
// Reuse snippet pattern
|
|
511
|
+
children: (el, s, ctx) => el.getSnippet('dataMockSnippet').data
|
|
512
|
+
// or in rare occasions
|
|
513
|
+
children: (el, s, ctx) => ctx.snippets['dataMockSnippet'].data
|
|
514
|
+
},
|
|
515
|
+
Title: {
|
|
516
|
+
text: 'Hello'
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## Root Index Export (`/smbls/index.js`)
|
|
524
|
+
|
|
525
|
+
**Pattern:**
|
|
526
|
+
|
|
527
|
+
```javascript
|
|
528
|
+
// smbls/index.js
|
|
529
|
+
export { default as config } from "./config.js";
|
|
530
|
+
export { default as vars } from "./vars.js";
|
|
531
|
+
export { default as dependencies } from "./dependencies.js";
|
|
532
|
+
export { default as designSystem } from "./designSystem/index.js";
|
|
533
|
+
export { default as state } from "./state/index.js";
|
|
534
|
+
export { default as files } from "./files/index.js";
|
|
535
|
+
export { default as pages } from "./pages/index.js";
|
|
536
|
+
export * as components from "./components/index.js";
|
|
537
|
+
export * as snippets from "./snippets/index.js";
|
|
538
|
+
export * as functions from "./functions/index.js";
|
|
539
|
+
export * as methods from "./methods/index.js";
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Key Rules:**
|
|
543
|
+
|
|
544
|
+
- Central export point for entire project
|
|
545
|
+
- Exposes all modules with consistent naming
|
|
546
|
+
- Used by platform/framework to load the application
|
|
547
|
+
- Maintains structure for environment-agnostic loading
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## Import Restrictions by Environment
|
|
552
|
+
|
|
553
|
+
### ✅ ALLOWED Imports
|
|
554
|
+
|
|
555
|
+
1. **Within same folder** (component to component via tree)
|
|
556
|
+
|
|
557
|
+
```javascript
|
|
558
|
+
// No imports needed - reference by name
|
|
559
|
+
{
|
|
560
|
+
Header: { },
|
|
561
|
+
Content: { Search: { } }
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
2. **Function calls**
|
|
566
|
+
|
|
567
|
+
```javascript
|
|
568
|
+
el.call("functionName", args);
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
3. **Method calls**
|
|
572
|
+
```javascript
|
|
573
|
+
el.methodName();
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### ❌ FORBIDDEN Imports
|
|
577
|
+
|
|
578
|
+
```javascript
|
|
579
|
+
// DON'T DO THIS:
|
|
580
|
+
import { Header } from "./Header.js";
|
|
581
|
+
import { parseNetworkRow } from "../functions/parseNetworkRow.js";
|
|
582
|
+
import styles from "./style.css";
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
No JavaScript `import`/`require` statements for components, functions, or methods within the project structure.
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## File Structure Rules
|
|
590
|
+
|
|
591
|
+
### Naming Conventions
|
|
592
|
+
|
|
593
|
+
| Location | Filename | Export | Type |
|
|
594
|
+
| --------------- | -------------------- | ----------------------------------------------- | --------------------- |
|
|
595
|
+
| `components/` | `Header.js` | `export const Header = { }` | PascalCase |
|
|
596
|
+
| `pages/` | `add-network.js` | `export const addNetwork = { }` | dash-case / camelCase |
|
|
597
|
+
| `functions/` | `parseNetworkRow.js` | `export const parseNetworkRow = function() { }` | camelCase |
|
|
598
|
+
| `methods/` | `formatDate.js` | `export const formatDate = function() { }` | camelCase |
|
|
599
|
+
| `designSystem/` | `color.js` | `export default { }` | camelCase |
|
|
600
|
+
| `snippets/` | `cardSnippet.js` | `export const cardSnippet = { }` | camelCase |
|
|
601
|
+
| `state/` | `metrics.js` | `export default [ ]` | camelCase |
|
|
602
|
+
| Root | `vars.js` | `export default { }` | camelCase |
|
|
603
|
+
| Root | `config.js` | `export default { }` | camelCase |
|
|
604
|
+
| Root | `dependencies.js` | `export default { }` | camelCase |
|
|
605
|
+
| Root | `files.js` | `export default { }` | camelCase |
|
|
606
|
+
|
|
607
|
+
### Index Files & Root Exports
|
|
608
|
+
|
|
609
|
+
**[components/index.js](smbls/components/index.js):**
|
|
610
|
+
|
|
611
|
+
```javascript
|
|
612
|
+
// CORRECT — flat re-exports, NOT 'export * as'
|
|
613
|
+
export * from "./ComponentName.js";
|
|
614
|
+
export * from "./AnotherComponent.js";
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
> ⚠️ NEVER use `export * as ComponentName from '...'` — it wraps exports in a namespace object and breaks component resolution entirely.
|
|
618
|
+
|
|
619
|
+
**[functions/index.js](smbls/functions/index.js):**
|
|
620
|
+
|
|
621
|
+
```javascript
|
|
622
|
+
export * from "./functionName.js";
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
**[pages/index.js](smbls/pages/index.js):**
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
import { main } from "./main";
|
|
629
|
+
import { addNetwork } from "./add-network";
|
|
630
|
+
import { editNode } from "./edit-node";
|
|
631
|
+
|
|
632
|
+
export default {
|
|
633
|
+
"/": main,
|
|
634
|
+
"/add-network": addNetwork,
|
|
635
|
+
"/edit-node": editNode,
|
|
636
|
+
// ... route mappings
|
|
637
|
+
};
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**[snippets/index.js](smbls/snippets/index.js):**
|
|
641
|
+
|
|
642
|
+
```javascript
|
|
643
|
+
export * from "./snippet1.js";
|
|
644
|
+
export * from "./snippet2.js";
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**[designSystem/index.js](smbls/designSystem/index.js):**
|
|
648
|
+
|
|
649
|
+
```javascript
|
|
650
|
+
import ANIMATION from "./animation.js";
|
|
651
|
+
import COLOR from "./color.js";
|
|
652
|
+
import SPACING from "./spacing.js";
|
|
653
|
+
// ... all design system modules
|
|
654
|
+
|
|
655
|
+
export { ANIMATION, COLOR, SPACING };
|
|
656
|
+
|
|
657
|
+
export default {
|
|
658
|
+
ANIMATION,
|
|
659
|
+
COLOR,
|
|
660
|
+
SPACING,
|
|
661
|
+
// ...
|
|
662
|
+
};
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## Component Definition Template
|
|
668
|
+
|
|
669
|
+
```javascript
|
|
670
|
+
export const ComponentName = {
|
|
671
|
+
// Extend from base component type
|
|
672
|
+
extends: "Flex", // or 'Box', 'Page', etc.
|
|
673
|
+
|
|
674
|
+
// Props: styling, behavior, event handlers
|
|
675
|
+
padding: "A",
|
|
676
|
+
theme: "dialog",
|
|
677
|
+
onClick: (el, s) => el.call("handleClick"),
|
|
678
|
+
onRender: async (el, s) => await el.call("fetchData"),
|
|
679
|
+
|
|
680
|
+
// Nested child components (no imports)
|
|
681
|
+
Header: {},
|
|
682
|
+
Content: {
|
|
683
|
+
Article: {
|
|
684
|
+
Title: { text: "Hello" },
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
Footer: {},
|
|
688
|
+
};
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## Component References: PascalCase Keys
|
|
694
|
+
|
|
695
|
+
**PascalCase keys automatically create components of that type.** You don't need to import them or use `extends:` when the key name matches the component name.
|
|
696
|
+
|
|
697
|
+
### ❌ INCORRECT: Verbose with imports and extends
|
|
698
|
+
|
|
699
|
+
```javascript
|
|
700
|
+
import { UpChart } from "./UpChart.js";
|
|
701
|
+
import { PeerCountChart } from "./PeerCountChart.js";
|
|
702
|
+
|
|
703
|
+
export const Graphs = {
|
|
704
|
+
UpChart: { extends: UpChart, props: { order: "1" } },
|
|
705
|
+
PeerCountChart: { extends: PeerCountChart, props: { flex: "1" } },
|
|
706
|
+
};
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
**Problems:**
|
|
710
|
+
|
|
711
|
+
- Unnecessary imports clutter the file
|
|
712
|
+
- Verbose `extends:` and `props:` wrapper
|
|
713
|
+
- Redundant when key name matches component name
|
|
714
|
+
|
|
715
|
+
### ✅ CORRECT: Clean PascalCase references
|
|
716
|
+
|
|
717
|
+
```javascript
|
|
718
|
+
export const Graphs = {
|
|
719
|
+
UpChart: { order: "1" },
|
|
720
|
+
PeerCountChart: { flex: "1" },
|
|
721
|
+
};
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
**Key Rules:**
|
|
725
|
+
|
|
726
|
+
- **Key name determines component type:** `UpChart:` automatically creates an `UpChart` component
|
|
727
|
+
- **No imports needed:** Component is referenced by name, not imported
|
|
728
|
+
- **Flatten props directly:** Put props inline, not in a `props:` wrapper
|
|
729
|
+
- **No `extends:` needed:** The key name is implicit `extends: ComponentName`
|
|
730
|
+
- **Works in all contexts:** Top-level, nested, or inside functions
|
|
731
|
+
|
|
732
|
+
### Example: Rows of Charts
|
|
733
|
+
|
|
734
|
+
```javascript
|
|
735
|
+
export const Graphs = {
|
|
736
|
+
extends: "Flex",
|
|
737
|
+
|
|
738
|
+
Row1: {
|
|
739
|
+
extends: "Flex",
|
|
740
|
+
flow: "x",
|
|
741
|
+
gap: "A",
|
|
742
|
+
|
|
743
|
+
// Each chart component is created by its PascalCase key
|
|
744
|
+
LatestBlockChart: { flex: "1" },
|
|
745
|
+
SyncingChart: { flex: "1" },
|
|
746
|
+
BlocksToSyncChart: { flex: "1" },
|
|
747
|
+
},
|
|
748
|
+
|
|
749
|
+
Row2: {
|
|
750
|
+
extends: "Flex",
|
|
751
|
+
flow: "x",
|
|
752
|
+
gap: "A",
|
|
753
|
+
|
|
754
|
+
PeerCountChart: { flex: "1" },
|
|
755
|
+
NetListeningChart: { flex: "1" },
|
|
756
|
+
},
|
|
757
|
+
};
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
This approach keeps component definitions clean and readable while maintaining the declarative architecture principle.
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## Event Handlers
|
|
765
|
+
|
|
766
|
+
```javascript
|
|
767
|
+
{
|
|
768
|
+
Button: {
|
|
769
|
+
onClick: (element, state) => {
|
|
770
|
+
// Call function: el.call('functionName', ...args)
|
|
771
|
+
element.call("parseData", rawData);
|
|
772
|
+
|
|
773
|
+
// Call method: el.methodName()
|
|
774
|
+
element.format();
|
|
775
|
+
|
|
776
|
+
// Update state: state.property = value
|
|
777
|
+
state.count++;
|
|
778
|
+
|
|
779
|
+
// Use design system tokens in inline styles
|
|
780
|
+
element.style.padding = "A"; // From designSystem/spacing
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
## Component Scope: Local Functions
|
|
789
|
+
|
|
790
|
+
When you need to define local helper functions within a component that are not meant for global reuse, use the `scope` property instead of importing external functions. This keeps the component self-contained and avoids creating unnecessary top-level imports.
|
|
791
|
+
|
|
792
|
+
### ❌ INCORRECT: Importing Local Functions
|
|
793
|
+
|
|
794
|
+
```javascript
|
|
795
|
+
// functions/fetchMetrics.js - If only used in one component!
|
|
796
|
+
const fetchMetrics = (timeRange) => {
|
|
797
|
+
// fetch logic
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
// components/Graphs.js
|
|
801
|
+
import { fetchMetrics } from "../functions/fetchMetrics.js"; // WRONG!
|
|
802
|
+
|
|
803
|
+
export const Graphs = {
|
|
804
|
+
extends: "Box",
|
|
805
|
+
onInit: (el) => {
|
|
806
|
+
fetchMetrics("week");
|
|
807
|
+
},
|
|
808
|
+
};
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
**Problems:**
|
|
812
|
+
|
|
813
|
+
- Creates unnecessary separate files for component-specific logic
|
|
814
|
+
- Mixes component-specific and globally-reusable code
|
|
815
|
+
- Adds imports where they should be avoided
|
|
816
|
+
|
|
817
|
+
### ✅ CORRECT: Use Component Scope
|
|
818
|
+
|
|
819
|
+
```javascript
|
|
820
|
+
// components/Graphs.js
|
|
821
|
+
export const Graphs = {
|
|
822
|
+
extends: "Box",
|
|
823
|
+
|
|
824
|
+
// Define local functions in scope property
|
|
825
|
+
scope: {
|
|
826
|
+
fetchMetrics: (timeRange) => {
|
|
827
|
+
// fetch logic here - not exported, only used locally
|
|
828
|
+
return fetch(`/api/metrics?range=${timeRange}`).then((res) => res.json());
|
|
829
|
+
},
|
|
830
|
+
|
|
831
|
+
calculateAverage: (data) => {
|
|
832
|
+
return data.reduce((a, b) => a + b, 0) / data.length;
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
|
|
836
|
+
// Use scope functions in event handlers
|
|
837
|
+
onInit: (el) => {
|
|
838
|
+
const metrics = el.scope.fetchMetrics("week");
|
|
839
|
+
const avg = el.scope.calculateAverage(metrics);
|
|
840
|
+
},
|
|
841
|
+
|
|
842
|
+
Button: {
|
|
843
|
+
onClick: (el) => {
|
|
844
|
+
el.scope.fetchMetrics("month");
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
};
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
**Key Rules for Scope:**
|
|
851
|
+
|
|
852
|
+
- `scope` property contains **local helper functions** for this component only
|
|
853
|
+
- Functions in scope are **never exported** and **never imported elsewhere**
|
|
854
|
+
- Access scope functions via `el.scope.functionName()`
|
|
855
|
+
- Use `scope` only for component-specific logic; use `functions/` for reusable utilities
|
|
856
|
+
- Scope functions receive only the data they need (element context is available via `el`)
|
|
857
|
+
|
|
858
|
+
### Scope vs Functions Folder
|
|
859
|
+
|
|
860
|
+
**Use `scope` when:**
|
|
861
|
+
|
|
862
|
+
- Function is only used within one component
|
|
863
|
+
- Function is a helper for that specific component's logic
|
|
864
|
+
- Keeping code co-located improves readability
|
|
865
|
+
|
|
866
|
+
**Use `functions/` when:**
|
|
867
|
+
|
|
868
|
+
- Function is reused across multiple components
|
|
869
|
+
- Function is a general utility (parsing, calculations, data fetching patterns)
|
|
870
|
+
- Function can be tested independently
|
|
871
|
+
|
|
872
|
+
### Example: Multiple Scope Functions
|
|
873
|
+
|
|
874
|
+
```javascript
|
|
875
|
+
export const MetricsPage = {
|
|
876
|
+
extends: "Page",
|
|
877
|
+
|
|
878
|
+
scope: {
|
|
879
|
+
// Local helpers
|
|
880
|
+
fetchMetrics: async (type) => {
|
|
881
|
+
const res = await fetch(`/api/metrics/${type}`);
|
|
882
|
+
return res.json();
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
formatMetric: (value) => {
|
|
886
|
+
return new Intl.NumberFormat().format(value);
|
|
887
|
+
},
|
|
888
|
+
|
|
889
|
+
filterByEnvironment: (data, env) => {
|
|
890
|
+
return data.filter((item) => item.environment === env);
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
|
|
894
|
+
onInit: async (el, state) => {
|
|
895
|
+
const data = await el.scope.fetchMetrics("daily");
|
|
896
|
+
state.metrics = el.scope.filterByEnvironment(data, "production");
|
|
897
|
+
},
|
|
898
|
+
|
|
899
|
+
MetricsChart: {
|
|
900
|
+
onRender: (el, state) => {
|
|
901
|
+
const formatted = state.metrics.map((m) =>
|
|
902
|
+
el.scope.formatMetric(m.value),
|
|
903
|
+
);
|
|
904
|
+
el.data = formatted;
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
};
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
### Calling Global Functions from Scope
|
|
911
|
+
|
|
912
|
+
When a scope function needs to call a global function from the `functions/` folder, use `el.call()` instead of importing. **Pass `el` as the first parameter to the scope function so it has access to element methods.**
|
|
913
|
+
|
|
914
|
+
**Pattern:**
|
|
915
|
+
|
|
916
|
+
```javascript
|
|
917
|
+
export const Graphs = {
|
|
918
|
+
extends: "Flex",
|
|
919
|
+
|
|
920
|
+
scope: {
|
|
921
|
+
// Scope function receives el as first parameter
|
|
922
|
+
fetchMetrics: (el, s, timeRange) => {
|
|
923
|
+
const networkName = (s.protocol || "").toLowerCase();
|
|
924
|
+
|
|
925
|
+
s.update({ metricsLoading: true });
|
|
926
|
+
|
|
927
|
+
// Call global function via el.call() - no import needed
|
|
928
|
+
el.call("apiFetch", "POST", "/api/metrics", {
|
|
929
|
+
networkName,
|
|
930
|
+
timeRangeMinutes: timeRange || 5,
|
|
931
|
+
})
|
|
932
|
+
.then((data) => {
|
|
933
|
+
s.update({ metricsData: data, metricsLoading: false });
|
|
934
|
+
})
|
|
935
|
+
.catch((err) => {
|
|
936
|
+
console.error("Failed to fetch:", err);
|
|
937
|
+
s.update({ metricsLoading: false });
|
|
938
|
+
});
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
|
|
942
|
+
onInit: (el, s) => {
|
|
943
|
+
// Pass el as first argument when calling scope function
|
|
944
|
+
el.scope.fetchMetrics(el, s, 5);
|
|
945
|
+
},
|
|
946
|
+
};
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
**Key Rules:**
|
|
950
|
+
|
|
951
|
+
- **No imports needed** - Call global functions via `el.call('functionName', ...args)`
|
|
952
|
+
- **Pass `el` as first parameter** - Scope functions need element access to call `el.call()`
|
|
953
|
+
- **Update all call sites** - When calling the scope function, pass `el` as first argument
|
|
954
|
+
- **Keep scope for local logic only** - Use this pattern only in scope functions that need global utilities
|
|
955
|
+
- **No async/await in el.call()** - Handle promises with `.then()` and `.catch()`
|
|
956
|
+
|
|
957
|
+
---
|
|
958
|
+
|
|
959
|
+
## Environment Compatibility
|
|
960
|
+
|
|
961
|
+
This structure works seamlessly in:
|
|
962
|
+
|
|
963
|
+
- **VSCode** - Individual files can be edited independently
|
|
964
|
+
- **Symbols Platform** - Declarative trees render directly
|
|
965
|
+
- **File Structure** - Pure JavaScript, no build step required
|
|
966
|
+
- **Server Rendering** - Stateless object definitions
|
|
967
|
+
- **Web Browser** - DOMQL runtime interprets the tree
|
|
968
|
+
|
|
969
|
+
**No special handling needed** - the same files work everywhere.
|
|
970
|
+
|
|
971
|
+
---
|
|
972
|
+
|
|
973
|
+
## DO's and DON'Ts
|
|
974
|
+
|
|
975
|
+
### ✅ DO:
|
|
976
|
+
|
|
977
|
+
- Name files exactly as their export names
|
|
978
|
+
- Use declarative object syntax for all components and pages
|
|
979
|
+
- Components MUST be plain JavaScript objects, never functions
|
|
980
|
+
- Reference components by name within the tree
|
|
981
|
+
- Use `el.call('functionName')` for utilities
|
|
982
|
+
- Use design system shorthand in props
|
|
983
|
+
- Keep components stateless (state managed externally)
|
|
984
|
+
- Place all component logic into props handlers
|
|
985
|
+
- Keep all folders flat - no subfolders within components/, functions/, etc.
|
|
986
|
+
|
|
987
|
+
### ❌ DON'T:
|
|
988
|
+
|
|
989
|
+
- Import components as JavaScript modules
|
|
990
|
+
- Use `import/require` for project files
|
|
991
|
+
- Create class-based components
|
|
992
|
+
- **Use component functions** - components must be objects, never callable functions
|
|
993
|
+
- Mix framework-specific code (React, Vue, etc.)
|
|
994
|
+
- Mutate element directly (use state instead)
|
|
995
|
+
- Create circular dependencies between files
|
|
996
|
+
- Use default exports for components (use named exports)
|
|
997
|
+
- **Create subfolders** - Anti-pattern to have `components/charts/`, `functions/api/`, etc. All files must be flat
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
## Flat Folder Structure (No Subfolders)
|
|
1002
|
+
|
|
1003
|
+
All folders must remain completely flat. Creating subfolders is an anti-pattern that breaks the architecture.
|
|
1004
|
+
|
|
1005
|
+
### ❌ INCORRECT: Organizing with Subfolders
|
|
1006
|
+
|
|
1007
|
+
```javascript
|
|
1008
|
+
// WRONG - Don't do this!
|
|
1009
|
+
components/
|
|
1010
|
+
├── charts/
|
|
1011
|
+
│ ├── LineChart.js
|
|
1012
|
+
│ ├── BarChart.js
|
|
1013
|
+
│ └── PieChart.js
|
|
1014
|
+
├── forms/
|
|
1015
|
+
│ ├── LoginForm.js
|
|
1016
|
+
│ ├── RegisterForm.js
|
|
1017
|
+
│ └── ContactForm.js
|
|
1018
|
+
└── Button.js
|
|
1019
|
+
|
|
1020
|
+
functions/
|
|
1021
|
+
├── api/
|
|
1022
|
+
│ ├── fetchUser.js
|
|
1023
|
+
│ ├── fetchPosts.js
|
|
1024
|
+
│ └── deleteUser.js
|
|
1025
|
+
├── math/
|
|
1026
|
+
│ └── calculateCosts.js
|
|
1027
|
+
└── ...
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
**Problems:**
|
|
1031
|
+
|
|
1032
|
+
- Import paths become complex and variable
|
|
1033
|
+
- File registry in `index.js` becomes harder to maintain
|
|
1034
|
+
- Path resolution differs between environments
|
|
1035
|
+
- Contradicts flat, declarative principle
|
|
1036
|
+
- Makes search and discovery harder
|
|
1037
|
+
|
|
1038
|
+
### ✅ CORRECT: Completely Flat Structure
|
|
1039
|
+
|
|
1040
|
+
```javascript
|
|
1041
|
+
// RIGHT - Always do this
|
|
1042
|
+
components/
|
|
1043
|
+
├── index.js
|
|
1044
|
+
├── Header.js
|
|
1045
|
+
├── Button.js
|
|
1046
|
+
├── LineChart.js
|
|
1047
|
+
├── BarChart.js
|
|
1048
|
+
├── PieChart.js
|
|
1049
|
+
├── LoginForm.js
|
|
1050
|
+
├── RegisterForm.js
|
|
1051
|
+
├── ContactForm.js
|
|
1052
|
+
└── ...
|
|
1053
|
+
|
|
1054
|
+
functions/
|
|
1055
|
+
├── index.js
|
|
1056
|
+
├── parseNetworkRow.js
|
|
1057
|
+
├── calculateCosts.js
|
|
1058
|
+
├── fetchUser.js
|
|
1059
|
+
├── fetchPosts.js
|
|
1060
|
+
├── deleteUser.js
|
|
1061
|
+
└── ...
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
**Advantages:**
|
|
1065
|
+
|
|
1066
|
+
- Simple, predictable file structure
|
|
1067
|
+
- Consistent import paths: `./ComponentName.js`
|
|
1068
|
+
- Easy to maintain and search
|
|
1069
|
+
- Works identically in all environments
|
|
1070
|
+
- Clear naming eliminates need for folders
|
|
1071
|
+
- All files registered at the same level in `index.js`
|
|
1072
|
+
|
|
1073
|
+
### Naming Strategy Instead of Folders:
|
|
1074
|
+
|
|
1075
|
+
Use **descriptive filenames** instead of subfolders to organize conceptually-related files:
|
|
1076
|
+
|
|
1077
|
+
```javascript
|
|
1078
|
+
// components/ - Chart-related components
|
|
1079
|
+
ChartContainer.js;
|
|
1080
|
+
LineChart.js;
|
|
1081
|
+
BarChart.js;
|
|
1082
|
+
PieChart.js;
|
|
1083
|
+
ChartTooltip.js;
|
|
1084
|
+
ChartLegend.js;
|
|
1085
|
+
|
|
1086
|
+
// functions/ - Function-related APIs
|
|
1087
|
+
fetchUserProfile.js;
|
|
1088
|
+
fetchUserPosts.js;
|
|
1089
|
+
deleteUserAccount.js;
|
|
1090
|
+
createNewPost.js;
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
Each filename clearly indicates its purpose without needing folder organization.
|
|
1094
|
+
|
|
1095
|
+
---
|
|
1096
|
+
|
|
1097
|
+
## Components are Objects, Not Functions
|
|
1098
|
+
|
|
1099
|
+
This is a critical architectural principle. Components must ALWAYS be plain JavaScript objects, never functions.
|
|
1100
|
+
|
|
1101
|
+
### ❌ INCORRECT: Component as a Function
|
|
1102
|
+
|
|
1103
|
+
```javascript
|
|
1104
|
+
// WRONG - Don't do this!
|
|
1105
|
+
export const Header = (element, state) => ({
|
|
1106
|
+
border: "1px solid black",
|
|
1107
|
+
padding: "A",
|
|
1108
|
+
Title: {
|
|
1109
|
+
text: "Hello",
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
**Problems:**
|
|
1115
|
+
|
|
1116
|
+
- Runtime recalculation on every access
|
|
1117
|
+
- Stateful behavior contradicts declarative principle
|
|
1118
|
+
- Cannot be properly registered and cached
|
|
1119
|
+
- Breaks environment-agnostic loading
|
|
1120
|
+
|
|
1121
|
+
### ✅ CORRECT: Component as a Plain Object
|
|
1122
|
+
|
|
1123
|
+
```javascript
|
|
1124
|
+
// RIGHT - Always do this
|
|
1125
|
+
export const Header = {
|
|
1126
|
+
border: "1px solid black",
|
|
1127
|
+
padding: "A",
|
|
1128
|
+
Title: {
|
|
1129
|
+
text: "Hello",
|
|
1130
|
+
},
|
|
1131
|
+
onClick: (element, state) => {
|
|
1132
|
+
// Logic goes in handlers, not in the object definition
|
|
1133
|
+
state.count++;
|
|
1134
|
+
},
|
|
1135
|
+
};
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
**Why:**
|
|
1139
|
+
|
|
1140
|
+
- Static, declarative definition
|
|
1141
|
+
- Resolvable without runtime execution
|
|
1142
|
+
- Cacheable and registrable
|
|
1143
|
+
- Works in all environments (VSCode, server, browser, Symbols platform)
|
|
1144
|
+
- Handlers can contain logic at runtime
|
|
1145
|
+
|
|
1146
|
+
### Key Point:
|
|
1147
|
+
|
|
1148
|
+
**Only property values can be functions (event handlers, getters), never the component itself.**
|
|
1149
|
+
|
|
1150
|
+
---
|
|
1151
|
+
|
|
1152
|
+
## Example: Building a Feature
|
|
1153
|
+
|
|
1154
|
+
### Task: Create a User Profile Card Component
|
|
1155
|
+
|
|
1156
|
+
**1. Create** `components/UserCard.js`:
|
|
1157
|
+
|
|
1158
|
+
```javascript
|
|
1159
|
+
export const UserCard = {
|
|
1160
|
+
extends: "Box",
|
|
1161
|
+
padding: "A2",
|
|
1162
|
+
round: "A",
|
|
1163
|
+
background: "white",
|
|
1164
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
|
1165
|
+
Avatar: {
|
|
1166
|
+
boxSize: "C",
|
|
1167
|
+
},
|
|
1168
|
+
Name: {
|
|
1169
|
+
fontSize: "L",
|
|
1170
|
+
fontWeight: "600",
|
|
1171
|
+
},
|
|
1172
|
+
Email: {
|
|
1173
|
+
fontSize: "S",
|
|
1174
|
+
color: "caption",
|
|
1175
|
+
},
|
|
1176
|
+
Actions: {
|
|
1177
|
+
Edit: {
|
|
1178
|
+
onClick: (el) => el.call("editUser"),
|
|
1179
|
+
},
|
|
1180
|
+
Delete: {
|
|
1181
|
+
onClick: (el) => el.call("deleteUser"),
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
};
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
**2. Create** `functions/editUser.js`:
|
|
1188
|
+
|
|
1189
|
+
```javascript
|
|
1190
|
+
export const editUser = function (userId) {
|
|
1191
|
+
return fetch(`/api/users/${userId}`, { method: "GET" }).then((res) =>
|
|
1192
|
+
res.json(),
|
|
1193
|
+
);
|
|
1194
|
+
};
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
**3. Use in page** `pages/profile.js`:
|
|
1198
|
+
|
|
1199
|
+
```javascript
|
|
1200
|
+
export const profile = {
|
|
1201
|
+
extends: "Page",
|
|
1202
|
+
UserCard: {
|
|
1203
|
+
// No import needed!
|
|
1204
|
+
},
|
|
1205
|
+
};
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
**4. Register in** `components/index.js`:
|
|
1209
|
+
|
|
1210
|
+
```javascript
|
|
1211
|
+
// CORRECT — flat re-export
|
|
1212
|
+
export * from "./UserCard.js";
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
---
|
|
1216
|
+
|
|
1217
|
+
## Summary
|
|
1218
|
+
|
|
1219
|
+
This is a **strict, declarative architecture** where:
|
|
1220
|
+
|
|
1221
|
+
1. **Each file exports one independent object** (component, function, or method)
|
|
1222
|
+
2. **No JavaScript imports between project files** - Everything is registered in index files
|
|
1223
|
+
3. **Components reference each other by name** in the object tree
|
|
1224
|
+
4. **Functions/methods are called at runtime** via `element.call()` and `element.method()`
|
|
1225
|
+
5. **Design tokens are available globally** through shorthand notation
|
|
1226
|
+
6. **Works everywhere** - VSCode, Symbols platform, servers, browsers - without modification
|
|
1227
|
+
|
|
1228
|
+
The design enables **framework-agnostic, environment-independent code** while maintaining **strict structure and conventions**.
|
|
1229
|
+
|
|
1230
|
+
---
|
|
1231
|
+
|
|
1232
|
+
## Complete Project Structure Overview
|
|
1233
|
+
|
|
1234
|
+
```
|
|
1235
|
+
smbls/
|
|
1236
|
+
├── index.js # Root export (central loader)
|
|
1237
|
+
├── vars.js # Global variables/constants
|
|
1238
|
+
├── config.js # Platform configuration
|
|
1239
|
+
├── dependencies.js # External npm packages (fixed versions)
|
|
1240
|
+
│
|
|
1241
|
+
├── components/ # UI Components (PascalCase files)
|
|
1242
|
+
│ ├── index.js # Component registry
|
|
1243
|
+
│ ├── Header.js
|
|
1244
|
+
│ ├── Button.js
|
|
1245
|
+
│ ├── Modal.js
|
|
1246
|
+
│ └── ...
|
|
1247
|
+
│
|
|
1248
|
+
├── pages/ # Page layouts (dash-case files)
|
|
1249
|
+
│ ├── index.js # Route mapping
|
|
1250
|
+
│ ├── main.js
|
|
1251
|
+
│ ├── add-network.js # dash-case filename, camelCase export
|
|
1252
|
+
│ ├── edit-node.js
|
|
1253
|
+
│ ├── dashboard.js
|
|
1254
|
+
│ └── ...
|
|
1255
|
+
│
|
|
1256
|
+
├── functions/ # Utility functions (camelCase)
|
|
1257
|
+
│ ├── index.js # Function exports
|
|
1258
|
+
│ ├── parseNetworkRow.js
|
|
1259
|
+
│ ├── calculateCosts.js
|
|
1260
|
+
│ └── ...
|
|
1261
|
+
│
|
|
1262
|
+
├── methods/ # Element methods (camelCase)
|
|
1263
|
+
│ ├── index.js
|
|
1264
|
+
│ └── ...
|
|
1265
|
+
│
|
|
1266
|
+
├── state/ # State and data (flat folder)
|
|
1267
|
+
│ ├── index.js # State registry
|
|
1268
|
+
│ ├── metrics.js
|
|
1269
|
+
│ ├── user.js
|
|
1270
|
+
│ ├── fleet.js
|
|
1271
|
+
│ └── ...
|
|
1272
|
+
│
|
|
1273
|
+
├── files/ # File assets (flat folder)
|
|
1274
|
+
│ ├── index.js # Files registry
|
|
1275
|
+
│ ├── images.js
|
|
1276
|
+
│ ├── logos.js
|
|
1277
|
+
│ └── ...
|
|
1278
|
+
│
|
|
1279
|
+
├── designSystem/ # Design tokens (flat folder)
|
|
1280
|
+
│ ├── index.js # Token registry with namespaces
|
|
1281
|
+
│ ├── color.js # Color tokens
|
|
1282
|
+
│ ├── spacing.js # Spacing/sizing scale
|
|
1283
|
+
│ ├── typography.js # Typography definitions
|
|
1284
|
+
│ ├── grid.js # Grid system
|
|
1285
|
+
│ ├── theme.js # Theme definitions
|
|
1286
|
+
│ ├── fonts.js # Font definitions
|
|
1287
|
+
│ ├── icons.js # SVG icons (flat)
|
|
1288
|
+
│ ├── animation.js # Animation definitions
|
|
1289
|
+
│ ├── timing.js # Timing values
|
|
1290
|
+
│ └── ...
|
|
1291
|
+
│
|
|
1292
|
+
└── snippets/ # Reusable code/data (any type)
|
|
1293
|
+
├── index.js # Snippet registry
|
|
1294
|
+
├── cardSnippet.js # Component snippets
|
|
1295
|
+
├── mockData.js # Mock data
|
|
1296
|
+
├── constants.js # Constants/enums
|
|
1297
|
+
├── utils.js # Utility functions
|
|
1298
|
+
├── response.json # Response examples
|
|
1299
|
+
└── ...
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
**Key Structural Points:**
|
|
1303
|
+
|
|
1304
|
+
- `state/` and `files/` are **folders with index.js registry**, not single files
|
|
1305
|
+
- All folders are **completely flat** - no subfolders within any folder
|
|
1306
|
+
- `designSystem/` is completely flat with separate files for each design aspect
|
|
1307
|
+
- `snippets/` can contain ANY type of JavaScript: components, functions, data, JSON
|
|
1308
|
+
- Clear naming conventions eliminate need for folder organization
|
|
1309
|
+
|
|
1310
|
+
---
|
|
1311
|
+
|
|
1312
|
+
## Zero Compilation Principle
|
|
1313
|
+
|
|
1314
|
+
**This structure is completely resolvable without any build step, compilation, or bundling.**
|
|
1315
|
+
|
|
1316
|
+
### Why It Works Everywhere:
|
|
1317
|
+
|
|
1318
|
+
1. **Pure JavaScript Objects** - No JSX, no preprocessing, just plain object literals
|
|
1319
|
+
2. **Dynamic Imports Only in Handlers** - NPM packages imported via `await import()` inside event listeners, never at module level
|
|
1320
|
+
3. **No Circular Dependencies** - Declarative tree structure prevents cyclic imports
|
|
1321
|
+
4. **Stateless by Design** - Each file is independent; state managed externally
|
|
1322
|
+
5. **Direct File Resolution** - No alias resolution needed; files imported directly by path
|
|
1323
|
+
|
|
1324
|
+
### Environment Loading:
|
|
1325
|
+
|
|
1326
|
+
- **VSCode:** Each `.js` file is a standalone module; syntax check works immediately
|
|
1327
|
+
- **Symbols Platform:** Parses the entire tree; renders components declaratively
|
|
1328
|
+
- **Browser:** DOMQL runtime loads and interprets the tree; imports handled by browser `import()`
|
|
1329
|
+
- **Server:** Node.js executes the tree; dynamic imports work natively
|
|
1330
|
+
- **File System:** Can be read and processed as plain JavaScript objects
|
|
1331
|
+
|
|
1332
|
+
**Result:** The exact same files work in all environments without modification.
|
|
1333
|
+
|
|
1334
|
+
---
|
|
1335
|
+
|
|
1336
|
+
## Best Practices Summary
|
|
1337
|
+
|
|
1338
|
+
### ✅ Best Practices:
|
|
1339
|
+
|
|
1340
|
+
- **One export per file** - Keeps structure clear and resolvable
|
|
1341
|
+
- **Declarative over imperative** - Use object structures, not code execution
|
|
1342
|
+
- **Import dependencies dynamically** - Only when needed in event handlers
|
|
1343
|
+
- **Reference components by name** - The tree handles all resolution
|
|
1344
|
+
- **Keep functions pure** - No side effects, only transformations
|
|
1345
|
+
- **Store state separately** - Never mutate component definitions
|
|
1346
|
+
- **Use design tokens** - Never hardcode styles or values
|
|
1347
|
+
- **Keep files small** - Easy to read, test, and maintain
|
|
1348
|
+
|
|
1349
|
+
### ❌ Anti-Patterns:
|
|
1350
|
+
|
|
1351
|
+
- Top-level `import`/`require` of project files
|
|
1352
|
+
- Class-based components or inheritance
|
|
1353
|
+
- Framework-specific syntax (React hooks, Vue composables)
|
|
1354
|
+
- Circular module dependencies
|
|
1355
|
+
- Hardcoded values in component definitions
|
|
1356
|
+
- Dynamic property names in object keys
|
|
1357
|
+
- Side effects in function definitions
|
|
1358
|
+
|
|
1359
|
+
---
|
|
1360
|
+
|
|
1361
|
+
## Migration Guide: Converting Old Projects
|
|
1362
|
+
|
|
1363
|
+
If converting from a standard module-based architecture:
|
|
1364
|
+
|
|
1365
|
+
1. **Remove all imports** of local project modules
|
|
1366
|
+
2. **Convert all components to plain objects** - No function wrappers, no factory functions
|
|
1367
|
+
3. **Convert props to declarative objects** - No function closures, just data
|
|
1368
|
+
4. **Move state to `state/` folder** - Centralize all mutable data
|
|
1369
|
+
5. **Replace function calls with `el.call()`** - Dynamic dispatch instead of imports
|
|
1370
|
+
6. **Move dynamic dependencies to handlers** - Use `await import()` inside events
|
|
1371
|
+
7. **Rename components to PascalCase** - Consistent with component convention
|
|
1372
|
+
8. **Update index files** - Register all exports centrally
|
|
1373
|
+
|
|
1374
|
+
Example transformation:
|
|
1375
|
+
|
|
1376
|
+
```javascript
|
|
1377
|
+
// OLD (Framework style)
|
|
1378
|
+
import Button from "./Button.js";
|
|
1379
|
+
import { handleClick } from "./handlers.js";
|
|
1380
|
+
|
|
1381
|
+
export const Header = () => {
|
|
1382
|
+
return <Button onClick={handleClick} />;
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
// NEW (Symbols style)
|
|
1386
|
+
// state/index.js
|
|
1387
|
+
export default { clickCount: 0 };
|
|
1388
|
+
|
|
1389
|
+
// components/Header.js
|
|
1390
|
+
export const Header = {
|
|
1391
|
+
Button: {
|
|
1392
|
+
onClick: (el, state) => {
|
|
1393
|
+
state.clickCount++;
|
|
1394
|
+
el.call("logClick", state.clickCount);
|
|
1395
|
+
},
|
|
1396
|
+
},
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
// functions/logClick.js
|
|
1400
|
+
export const logClick = function (count) {
|
|
1401
|
+
console.log("Clicks:", count);
|
|
1402
|
+
};
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
This architecture ensures **true independence** of components, **zero runtime compilation**, and **environment agnostic** execution.
|