@symbo.ls/mcp 1.0.21 → 1.0.22
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/bin/symbols-mcp.js +3 -2
- package/package.json +1 -1
- package/symbols_mcp/skills/AUDIT.md +1 -0
- package/symbols_mcp/skills/COMMON_MISTAKES.md +16 -0
- package/symbols_mcp/skills/COMPONENTS.md +2 -1
- package/symbols_mcp/skills/DEFAULT_COMPONENTS.md +2 -0
- package/symbols_mcp/skills/DESIGN_SYSTEM.md +68 -10
- package/symbols_mcp/skills/DESIGN_TO_CODE.md +1 -1
- package/symbols_mcp/skills/PATTERNS.md +13 -6
- package/symbols_mcp/skills/PROJECT_STRUCTURE.md +4 -3
- package/symbols_mcp/skills/RULES.md +21 -0
- package/symbols_mcp/skills/RUNNING_APPS.md +1 -0
- package/symbols_mcp/skills/SHARED_LIBRARIES.md +313 -0
- package/symbols_mcp/skills/SYNTAX.md +19 -6
package/bin/symbols-mcp.js
CHANGED
|
@@ -662,7 +662,7 @@ async function handleTool(name, args) {
|
|
|
662
662
|
// generate_page
|
|
663
663
|
if (name === 'generate_page') {
|
|
664
664
|
const pageName = args.page_name || 'home'
|
|
665
|
-
const context = readSkills('RULES.md', 'COMMON_MISTAKES.md', 'PROJECT_STRUCTURE.md', 'PATTERNS.md', 'SNIPPETS.md', 'DEFAULT_LIBRARY.md', 'COMPONENTS.md')
|
|
665
|
+
const context = readSkills('RULES.md', 'COMMON_MISTAKES.md', 'PROJECT_STRUCTURE.md', 'SHARED_LIBRARIES.md', 'PATTERNS.md', 'SNIPPETS.md', 'DEFAULT_LIBRARY.md', 'COMPONENTS.md')
|
|
666
666
|
return `# Generate Page: ${pageName}\n\n## Description\n${args.description}\n\n## Requirements\n- Export as: \`export const ${pageName} = { ... }\`\n- Page is a plain object composing components\n- Add to pages/index.js route map: \`'/${pageName}': ${pageName}\`\n- Use components by PascalCase key (Header, Footer, Hero, etc.)\n- **MANDATORY: ALL values MUST use design system tokens** — spacing (A, B, C, D), colors (primary, surface, white, gray.5), typography (fontSize: 'B'). ZERO px values, ZERO hex colors, ZERO rgb/hsl.\n- Use Icon component for SVGs — store icons in designSystem/icons\n- NO direct DOM manipulation — all structure via DOMQL declarative syntax\n- Include responsive layout adjustments\n\n## Context — Rules, Structure, Patterns & Snippets\n\n${context}`
|
|
667
667
|
}
|
|
668
668
|
|
|
@@ -692,7 +692,8 @@ async function handleTool(name, args) {
|
|
|
692
692
|
if (files.includes('index.html') && !files.includes('package.json') && !files.includes('symbols.json')) { envType = 'cdn'; confidence = 'medium' }
|
|
693
693
|
}
|
|
694
694
|
const guide = readSkill('RUNNING_APPS.md')
|
|
695
|
-
|
|
695
|
+
const sharedLibsGuide = envType === 'local_project' ? '\n\n## Shared Libraries\n\n' + readSkill('SHARED_LIBRARIES.md') : ''
|
|
696
|
+
return `# Environment Detection\n\n**Detected: ${envType}** (confidence: ${confidence})\n\n${guide}${sharedLibsGuide}`
|
|
696
697
|
}
|
|
697
698
|
|
|
698
699
|
// audit_component (sync)
|
package/package.json
CHANGED
|
@@ -202,6 +202,7 @@ Generate or update two files. Keep their scopes strictly separated.
|
|
|
202
202
|
Include only framework-level findings:
|
|
203
203
|
|
|
204
204
|
- DOMQL v3 violations
|
|
205
|
+
- UPPERCASE design system keys (COLOR, THEME, etc. — must be lowercase)
|
|
205
206
|
- Event handler misuse
|
|
206
207
|
- Atom/state mispatterns
|
|
207
208
|
- Shorthand inconsistencies
|
|
@@ -223,3 +223,19 @@ SpreadsheetCell: { fontSize: 'Z1', width: 'E', ... }
|
|
|
223
223
|
RowNumberCell: { fontSize: 'Z1', width: 'C', ... }
|
|
224
224
|
CornerCell: { fontSize: 'Z1', width: 'C', ... }
|
|
225
225
|
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 13. Design system keys — ALWAYS lowercase, never UPPERCASE
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
// ❌ WRONG — UPPERCASE keys are deprecated and banned
|
|
233
|
+
import { TYPOGRAPHY, SPACING } from '@symbo.ls/scratch'
|
|
234
|
+
const { COLOR, THEME } = context.designSystem
|
|
235
|
+
set({ COLOR: { blue: '#00f' }, TYPOGRAPHY: { base: 16 } })
|
|
236
|
+
|
|
237
|
+
// ✅ CORRECT — lowercase keys only
|
|
238
|
+
import { typography, spacing } from '@symbo.ls/scratch'
|
|
239
|
+
const { color, theme } = context.designSystem
|
|
240
|
+
set({ color: { blue: '#00f' }, typography: { base: 16 } })
|
|
241
|
+
```
|
|
@@ -65,7 +65,8 @@ All atoms support these additional features:
|
|
|
65
65
|
|---------|--------|
|
|
66
66
|
| Media queries | `@mobile`, `@tablet`, `@tabletSm`, `@dark`, `@light` |
|
|
67
67
|
| Pseudo selectors | `:hover`, `:active`, `:focus-visible` |
|
|
68
|
-
| Conditional cases | `.isActive`,
|
|
68
|
+
| Conditional cases | `.isActive`, `!isActive`, `$isSafari` (global from `context.cases`) |
|
|
69
|
+
| ARIA attributes | `ariaLabel`, `aria: { expanded: true }`, `'aria-label': 'Close'` |
|
|
69
70
|
| Child overrides | `childProps` — one-level child overrides |
|
|
70
71
|
| Children | `children` — arrays or nested object trees |
|
|
71
72
|
| Lifecycle events | `onInit`, `onRender`, `onUpdate`, `onStateUpdate` |
|
|
@@ -42,6 +42,7 @@ import pages from './pages/index.js'
|
|
|
42
42
|
import * as functions from './functions/index.js'
|
|
43
43
|
import * as methods from './methods/index.js'
|
|
44
44
|
import designSystem from './designSystem/index.js'
|
|
45
|
+
import cases from './cases.js'
|
|
45
46
|
import files from './files/index.js'
|
|
46
47
|
|
|
47
48
|
export default {
|
|
@@ -53,6 +54,7 @@ export default {
|
|
|
53
54
|
functions,
|
|
54
55
|
methods,
|
|
55
56
|
designSystem,
|
|
57
|
+
cases,
|
|
56
58
|
files
|
|
57
59
|
}
|
|
58
60
|
```
|
|
@@ -17,8 +17,8 @@ Design system config lives in `designSystem/`. All tokens resolve to CSS via DOM
|
|
|
17
17
|
| `class.js` | Utility CSS class overrides |
|
|
18
18
|
| `animation.js` | Named keyframe animations |
|
|
19
19
|
| `media.js` | Custom media query breakpoints |
|
|
20
|
-
| `cases.js` | Conditional environment flags |
|
|
21
20
|
| `reset.js` | Global CSS reset overrides |
|
|
21
|
+
| `vars.js` | Custom CSS properties (custom vars) |
|
|
22
22
|
|
|
23
23
|
## How Tokens Are Used in Props
|
|
24
24
|
|
|
@@ -488,16 +488,56 @@ Box: {
|
|
|
488
488
|
|
|
489
489
|
---
|
|
490
490
|
|
|
491
|
-
##
|
|
491
|
+
## Cases
|
|
492
492
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
493
|
+
Cases are defined in `symbols/cases.js` (not in designSystem) and added to `context.cases`. They are functions that evaluate conditions globally or per-element.
|
|
494
|
+
|
|
495
|
+
### Defining cases
|
|
496
|
+
|
|
497
|
+
```js
|
|
498
|
+
// symbols/cases.js
|
|
499
|
+
export default {
|
|
500
|
+
isSafari: () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
|
|
501
|
+
isGeorgian () { return this?.state?.root?.language === 'ka' },
|
|
502
|
+
isMobile: () => window.innerWidth < 768
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// symbols/context.js
|
|
506
|
+
import cases from './cases.js'
|
|
507
|
+
export default { cases, /* ...other context */ }
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Case functions receive `element` as `this` (and first arg), but must work without it (for global detection like `isSafari`).
|
|
511
|
+
|
|
512
|
+
### Using cases in components
|
|
496
513
|
|
|
497
514
|
```js
|
|
515
|
+
// $ prefix — global case from context.cases
|
|
498
516
|
Element: { $isSafari: { top: 'Z2', right: 'Z2' } }
|
|
517
|
+
|
|
518
|
+
// . prefix — props/state first, then context.cases
|
|
519
|
+
Button: { '.isActive': { background: 'blue', aria: { expanded: true } } }
|
|
520
|
+
|
|
521
|
+
// ! prefix — inverted
|
|
522
|
+
Card: { '!isMobile': { maxWidth: '1200px' } }
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Cases work in both CSS props (css-in-props) and HTML attributes (attrs-in-props).
|
|
526
|
+
|
|
527
|
+
## CSS Custom Properties (vars)
|
|
528
|
+
|
|
529
|
+
Define initial CSS custom properties in designSystem:
|
|
530
|
+
|
|
531
|
+
```js
|
|
532
|
+
vars: {
|
|
533
|
+
'--header-height': '60px',
|
|
534
|
+
'sidebar-width': '280px', // auto-prefixed to --sidebar-width
|
|
535
|
+
gap: '1rem' // becomes --gap
|
|
536
|
+
}
|
|
499
537
|
```
|
|
500
538
|
|
|
539
|
+
Reference in props: `padding: '--gap'` → resolves to `var(--gap)`. Any `--` prefixed value is auto-wrapped in `var()`.
|
|
540
|
+
|
|
501
541
|
---
|
|
502
542
|
|
|
503
543
|
## Design System Configuration
|
|
@@ -566,7 +606,7 @@ const designSystemConfig = {
|
|
|
566
606
|
```
|
|
567
607
|
color, gradient, theme, typography, spacing, timing,
|
|
568
608
|
font, font_family, icons, semantic_icons, svg, svg_data,
|
|
569
|
-
shadow, media, grid, class, reset, unit, animation,
|
|
609
|
+
shadow, media, grid, class, reset, unit, animation, vars
|
|
570
610
|
```
|
|
571
611
|
|
|
572
612
|
Do NOT wrap these under `props` or other wrappers.
|
|
@@ -599,13 +639,30 @@ font: {
|
|
|
599
639
|
|
|
600
640
|
```js
|
|
601
641
|
font: {
|
|
602
|
-
inter:
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
642
|
+
inter: [
|
|
643
|
+
{ url: '/fonts/Inter-Regular.woff2', fontWeight: 400 },
|
|
644
|
+
{ url: '/fonts/Inter-Bold.woff2', fontWeight: 700 }
|
|
645
|
+
]
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Multiple format fallbacks (array URL)
|
|
650
|
+
|
|
651
|
+
```js
|
|
652
|
+
font: {
|
|
653
|
+
Exo2: [
|
|
654
|
+
{
|
|
655
|
+
url: ['Exo2-Medium.woff2', 'Exo2-Medium.woff'],
|
|
656
|
+
fontWeight: '500',
|
|
657
|
+
fontStyle: 'normal',
|
|
658
|
+
fontDisplay: 'swap'
|
|
659
|
+
}
|
|
660
|
+
]
|
|
606
661
|
}
|
|
607
662
|
```
|
|
608
663
|
|
|
664
|
+
Generates comma-separated `src` with auto-detected formats per URL.
|
|
665
|
+
|
|
609
666
|
---
|
|
610
667
|
|
|
611
668
|
## Icons & SVG
|
|
@@ -709,6 +766,7 @@ updateVars({ color: { primary: '#ff0000' } }) // Update CSS variables only
|
|
|
709
766
|
|
|
710
767
|
## Common Mistakes
|
|
711
768
|
|
|
769
|
+
- **NEVER use UPPERCASE keys** (`COLOR`, `THEME`, `TYPOGRAPHY`, etc.) — always use lowercase (`color`, `theme`, `typography`). UPPERCASE is deprecated and banned.
|
|
712
770
|
- Do NOT nest config under `props` or other wrappers
|
|
713
771
|
- Use `font_family` not `fontFamily` in config
|
|
714
772
|
- Define `typography` and `spacing` if you use tokens like `A`, `B2`, or `C+Z`
|
|
@@ -23,7 +23,7 @@ Provide one or more of: design description, wireframe, screenshot, or component
|
|
|
23
23
|
|
|
24
24
|
- Complete, copy-paste ready Symbols component objects
|
|
25
25
|
- Responsive implementation using Symbols breakpoint syntax (`@tabletS`, `@mobileL`, etc.)
|
|
26
|
-
- Accessibility: semantic `tag` values, ARIA attributes
|
|
26
|
+
- Accessibility: semantic `tag` values, ARIA attributes via `aria: {}` shorthand / camelCase (`ariaLabel`) / kebab-case (`aria-label`)
|
|
27
27
|
- Loading states via `if` conditionals and state flags
|
|
28
28
|
- Animation via CSS transition properties on the component object
|
|
29
29
|
|
|
@@ -251,18 +251,25 @@ Box: { tag: 'div', text: 'Submit', onClick: fn } // div is not a button
|
|
|
251
251
|
```js
|
|
252
252
|
// Landmark roles
|
|
253
253
|
Box: { role: 'alert', text: 'Error occurred' }
|
|
254
|
-
Box: { role: 'status',
|
|
254
|
+
Box: { role: 'status', aria: { live: 'polite' }, text: '3 results found' }
|
|
255
255
|
|
|
256
|
-
// Labels
|
|
257
|
-
Input: {
|
|
258
|
-
Button: { icon: 'x',
|
|
259
|
-
Icon: { name: 'settings',
|
|
256
|
+
// Labels — three equivalent forms:
|
|
257
|
+
Input: { ariaLabel: 'Search networks' } // camelCase
|
|
258
|
+
Button: { icon: 'x', aria: { label: 'Close dialog' } } // object shorthand
|
|
259
|
+
Icon: { name: 'settings', 'aria-hidden': 'true' } // kebab-case
|
|
260
260
|
|
|
261
|
-
// Dynamic state
|
|
261
|
+
// Dynamic state via attr block (functions)
|
|
262
262
|
Button: {
|
|
263
263
|
attr: (el, s) => ({ 'aria-expanded': s.isOpen, 'aria-controls': 'dropdown-menu' }),
|
|
264
264
|
onClick: (e, el, s) => s.update({ isOpen: !s.isOpen })
|
|
265
265
|
}
|
|
266
|
+
|
|
267
|
+
// Dynamic state via conditional cases
|
|
268
|
+
Button: {
|
|
269
|
+
'.isOpen': { aria: { expanded: true } },
|
|
270
|
+
'!isOpen': { ariaHidden: true },
|
|
271
|
+
onClick: (e, el, s) => s.update({ isOpen: !s.isOpen })
|
|
272
|
+
}
|
|
266
273
|
```
|
|
267
274
|
|
|
268
275
|
### Keyboard Navigation
|
|
@@ -26,6 +26,7 @@ project-root/
|
|
|
26
26
|
└── symbols/ # Symbols frontend (client-side)
|
|
27
27
|
├── index.js # Root entry: exports components, pages, state, designSystem, functions
|
|
28
28
|
├── state.js # export default { key: initialValue, ... }
|
|
29
|
+
├── cases.js # export default { isSafari: () => {}, ... } — conditional cases
|
|
29
30
|
├── lang.js # Translations — root level, NOT in designSystem
|
|
30
31
|
├── dependencies.js # export default { 'pkg': 'exact-version' }
|
|
31
32
|
├── config.js # export default { useReset: true, fetch: { adapter: 'supabase', ... }, ... }
|
|
@@ -228,9 +229,9 @@ import theme from './theme.js'
|
|
|
228
229
|
export default { color, theme }
|
|
229
230
|
```
|
|
230
231
|
|
|
231
|
-
**What belongs here:** color, gradient, theme, font, typography, spacing, timing, grid, icons, shape, reset, animation, media,
|
|
232
|
+
**What belongs here:** color, gradient, theme, font, typography, spacing, timing, grid, icons, shape, reset, animation, media, vars.
|
|
232
233
|
|
|
233
|
-
**What does NOT belong here:** translations/lang (use root-level `lang.js`), application state, API config, business logic.
|
|
234
|
+
**What does NOT belong here:** translations/lang (use root-level `lang.js`), cases (use root-level `cases.js`), application state, API config, business logic.
|
|
234
235
|
|
|
235
236
|
See `DESIGN_SYSTEM.md` for the full token reference.
|
|
236
237
|
|
|
@@ -249,7 +250,7 @@ export default {
|
|
|
249
250
|
|
|
250
251
|
// symbols/context.js
|
|
251
252
|
import lang from './lang.js'
|
|
252
|
-
export default { lang, state, components, designSystem, ...config }
|
|
253
|
+
export default { lang, cases, state, components, designSystem, ...config }
|
|
253
254
|
```
|
|
254
255
|
|
|
255
256
|
For Supabase-backed translations, configure the `polyglot` key in `config.js`:
|
|
@@ -18,6 +18,27 @@ These rules are absolute. Violations cause silent failures (black page, nothing
|
|
|
18
18
|
| `children` + `childExtends` | ~~`$collection`, `$propsCollection`~~ |
|
|
19
19
|
| `children` + `childrenAs: 'state'` | ~~`$stateCollection`~~ |
|
|
20
20
|
| No `extends` needed for Text/Box/Flex; replace `extends: 'Flex'` with `flow: 'x'` or `flow: 'y'` | ~~`extends: 'Text'`~~, ~~`extends: 'Box'`~~, ~~`extends: 'Flex'`~~ |
|
|
21
|
+
| `color: {}`, `theme: {}`, `typography: {}` (lowercase) | ~~`COLOR: {}`, `THEME: {}`, `TYPOGRAPHY: {}`~~ (UPPERCASE) |
|
|
22
|
+
| `context.designSystem.color` | ~~`context.designSystem.COLOR`~~ |
|
|
23
|
+
| `import { typography } from '@symbo.ls/scratch'` | ~~`import { TYPOGRAPHY } from '@symbo.ls/scratch'`~~ |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Rule 0 — Design system keys are ALWAYS lowercase
|
|
28
|
+
|
|
29
|
+
UPPERCASE design system keys (`COLOR`, `THEME`, `TYPOGRAPHY`, `SPACING`, `TIMING`, `FONT`, `FONT_FAMILY`, `ICONS`, `SHADOW`, `MEDIA`, `GRID`, `ANIMATION`, `RESET`, `SVG`, `GRADIENT`, `SEMANTIC_ICONS`, `CASES`) are **deprecated and strictly banned**.
|
|
30
|
+
|
|
31
|
+
Always use lowercase: `color`, `theme`, `typography`, `spacing`, `timing`, `font`, `font_family`, `icons`, `shadow`, `media`, `grid`, `animation`, `reset`, `svg`, `gradient`, `vars`.
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
// ❌ BANNED
|
|
35
|
+
import { TYPOGRAPHY } from '@symbo.ls/scratch'
|
|
36
|
+
const { COLOR } = context.designSystem
|
|
37
|
+
|
|
38
|
+
// ✅ CORRECT
|
|
39
|
+
import { typography } from '@symbo.ls/scratch'
|
|
40
|
+
const { color } = context.designSystem
|
|
41
|
+
```
|
|
21
42
|
|
|
22
43
|
---
|
|
23
44
|
|
|
@@ -55,6 +55,7 @@ my-app/
|
|
|
55
55
|
├── pages/
|
|
56
56
|
│ ├── index.js # import-based route map (only file with imports)
|
|
57
57
|
│ └── main.js # export const main = { extends: 'Page', ... }
|
|
58
|
+
├── cases.js # export default { isSafari: () => {}, ... }
|
|
58
59
|
├── functions/
|
|
59
60
|
│ ├── index.js # export * from './myFn.js'
|
|
60
61
|
│ └── myFn.js # export const myFn = function() {}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# sharedLibraries
|
|
2
|
+
|
|
3
|
+
sharedLibraries is a mechanism in the Symbols framework that allows projects to inherit components, functions, methods, state, designSystem, pages, files, snippets, and dependencies from external library projects. Libraries are merged into the consuming app's context at runtime — no manual re-exports needed.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Configuration
|
|
8
|
+
|
|
9
|
+
### symbols.json
|
|
10
|
+
|
|
11
|
+
Three supported formats:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
// Array of strings (library keys)
|
|
15
|
+
"sharedLibraries": ["brand", "shared"]
|
|
16
|
+
|
|
17
|
+
// Object with key:version
|
|
18
|
+
"sharedLibraries": { "brand": "1.0.0", "shared": "latest" }
|
|
19
|
+
|
|
20
|
+
// Object with key:config (used in editor)
|
|
21
|
+
"sharedLibraries": {
|
|
22
|
+
"brand": { "version": "1.0.0", "destDir": "../brand" },
|
|
23
|
+
"shared": { "destDir": "../shared" }
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
All formats are normalized by the CLI (`cli/helpers/symbolsConfig.js:246-268`) to: `[{ key, version, destDir }]`
|
|
28
|
+
|
|
29
|
+
### sharedLibraries.js
|
|
30
|
+
|
|
31
|
+
Each package has a `sharedLibraries.js` that imports other packages' `context.js` and exports them as an array:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import brand from '../brand/context.js'
|
|
35
|
+
import shared from '../shared/context.js'
|
|
36
|
+
|
|
37
|
+
export default [brand, shared]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For remote libraries fetched from the platform:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import platformSymboLs from '../.symbols_local/libs/platform.symbo.ls/context.js'
|
|
44
|
+
import docsSymboLs from '../.symbols_local/libs/docs.symbo.ls/context.js'
|
|
45
|
+
|
|
46
|
+
export default [platformSymboLs, docsSymboLs]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### context.js
|
|
50
|
+
|
|
51
|
+
The `sharedLibraries` array is included in the context export:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import sharedLibraries from './sharedLibraries.js'
|
|
55
|
+
import * as components from './components/index.js'
|
|
56
|
+
import * as functions from './functions/index.js'
|
|
57
|
+
// ...
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
sharedLibraries,
|
|
61
|
+
components,
|
|
62
|
+
functions,
|
|
63
|
+
// ...
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Runtime Merge
|
|
70
|
+
|
|
71
|
+
### Entry Point
|
|
72
|
+
|
|
73
|
+
`smbls/src/createDomql.js:42-44`:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
if (context.sharedLibraries && context.sharedLibraries.length) {
|
|
77
|
+
prepareSharedLibs(context)
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This runs early in context initialization, **before** designSystem, state, components, and pages are prepared.
|
|
82
|
+
|
|
83
|
+
### Merge Logic
|
|
84
|
+
|
|
85
|
+
`smbls/src/prepare.js:240-251`:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
export const prepareSharedLibs = (context) => {
|
|
89
|
+
const sharedLibraries = context.sharedLibraries
|
|
90
|
+
for (let i = 0; i < sharedLibraries.length; i++) {
|
|
91
|
+
const sharedLib = sharedLibraries[i]
|
|
92
|
+
if (context.type === 'template') {
|
|
93
|
+
overwriteShallow(context.designSystem, sharedLib.designSystem)
|
|
94
|
+
deepMerge(context, sharedLib, ['designSystem'], 1)
|
|
95
|
+
} else {
|
|
96
|
+
deepMerge(context, sharedLib, [], 1)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Two strategies:**
|
|
103
|
+
|
|
104
|
+
| App Type | designSystem | Everything Else |
|
|
105
|
+
|----------|-------------|-----------------|
|
|
106
|
+
| **template** | `overwriteShallow` — library completely replaces template's designSystem | `deepMerge` with designSystem excluded |
|
|
107
|
+
| **regular** | `deepMerge` (app wins) | `deepMerge` (app wins) |
|
|
108
|
+
|
|
109
|
+
### deepMerge Behavior
|
|
110
|
+
|
|
111
|
+
`smbls/packages/utils/object.js:61-76`:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
export const deepMerge = (element, extend, excludeFrom = METHODS_EXL) => {
|
|
115
|
+
for (const e in extend) {
|
|
116
|
+
if (_startsWithDunder(e)) continue // skip __private
|
|
117
|
+
if (excludeFrom.includes(e)) continue // skip excluded keys
|
|
118
|
+
const elementProp = element[e]
|
|
119
|
+
const extendProp = extend[e]
|
|
120
|
+
if (isObjectLike(elementProp) && isObjectLike(extendProp)) {
|
|
121
|
+
deepMerge(elementProp, extendProp, excludeFrom) // recursive
|
|
122
|
+
} else if (elementProp === undefined) {
|
|
123
|
+
element[e] = extendProp // only fill undefined slots
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return element
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Key rules:
|
|
131
|
+
- **App always wins** — only merges when the app property is `undefined`
|
|
132
|
+
- Skips `__` prefixed properties
|
|
133
|
+
- Recursively merges nested objects
|
|
134
|
+
- Skips METHODS_EXL keys: `node`, `context`, `extends`, `__element`, `__ref`, and all element/state/props methods
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Order of Precedence
|
|
139
|
+
|
|
140
|
+
Libraries are processed sequentially. First library fills undefined slots, second fills remaining, etc.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
App's own context (highest priority — never overwritten)
|
|
144
|
+
↑
|
|
145
|
+
sharedLibraries[0] (fills undefined slots)
|
|
146
|
+
↑
|
|
147
|
+
sharedLibraries[1] (fills remaining undefined slots)
|
|
148
|
+
↑
|
|
149
|
+
UIKit atoms (lowest priority, applied in prepareComponents)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
After all merges, `prepareComponents` applies:
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
// smbls/src/prepare.js:51-55
|
|
156
|
+
export const prepareComponents = (context) => {
|
|
157
|
+
return context.components
|
|
158
|
+
? { ...UIkitWithPrefix(), ...context.components }
|
|
159
|
+
: UIkitWithPrefix()
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
So the final component resolution is: **App > Shared Libraries > UIKit**
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## What Gets Merged
|
|
168
|
+
|
|
169
|
+
| Merged | Not Merged (METHODS_EXL) |
|
|
170
|
+
|--------|--------------------------|
|
|
171
|
+
| components | node |
|
|
172
|
+
| functions | context |
|
|
173
|
+
| methods | extends |
|
|
174
|
+
| snippets | __element, __ref |
|
|
175
|
+
| pages / routes | Element methods (set, reset, update, remove, lookup...) |
|
|
176
|
+
| state | State methods (parse, create, destroy, toggle...) |
|
|
177
|
+
| designSystem | Props methods |
|
|
178
|
+
| files | Properties starting with `__` |
|
|
179
|
+
| dependencies | |
|
|
180
|
+
| dependenciesOnDemand | |
|
|
181
|
+
| utils, cases, plugins | |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## CLI Integration
|
|
186
|
+
|
|
187
|
+
### Scaffolding (`cli/bin/fs.js:231-345`)
|
|
188
|
+
|
|
189
|
+
`scaffoldSharedLibraries()`:
|
|
190
|
+
1. Reads `sharedLibraries` from project JSON
|
|
191
|
+
2. Creates each library as a full project folder via frank's `toFS()`
|
|
192
|
+
3. Default location: `.symbols_local/libs/`
|
|
193
|
+
4. Generates `sharedLibraries.js` with import statements
|
|
194
|
+
|
|
195
|
+
### Fetch (`cli/bin/fetch.js`)
|
|
196
|
+
|
|
197
|
+
When fetching from the platform:
|
|
198
|
+
- Pulls full project data including `sharedLibraries` array
|
|
199
|
+
- Each library is a complete Symbols project object
|
|
200
|
+
- Extracts version info and stores in lock file
|
|
201
|
+
|
|
202
|
+
### toJSON (`frank/toJSON.js:170-183`)
|
|
203
|
+
|
|
204
|
+
Context modules list includes sharedLibraries:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
const CONTEXT_MODULES = [
|
|
208
|
+
{ name: 'state', path: './state.js', style: 'default' },
|
|
209
|
+
{ name: 'dependencies', path: './dependencies.js', style: 'default' },
|
|
210
|
+
{ name: 'sharedLibraries', path: './sharedLibraries.js', style: 'default' },
|
|
211
|
+
{ name: 'components', path: './components/index.js', style: 'namespace' },
|
|
212
|
+
{ name: 'snippets', path: './snippets/index.js', style: 'namespace' },
|
|
213
|
+
{ name: 'pages', path: './pages/index.js', style: 'default' },
|
|
214
|
+
{ name: 'functions', path: './functions/index.js', style: 'namespace' },
|
|
215
|
+
{ name: 'methods', path: './methods/index.js', style: 'namespace' },
|
|
216
|
+
{ name: 'designSystem', path: './designSystem/index.js', style: 'default' },
|
|
217
|
+
{ name: 'files', path: './files/index.js', style: 'default' },
|
|
218
|
+
{ name: 'config', path: './config.js', style: 'default' }
|
|
219
|
+
]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Validation (`cli/bin/validate-domql-runner.js:323-342`)
|
|
223
|
+
|
|
224
|
+
`mergeSharedLibraries()` merges all library exports for DOMQL validation:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
function mergeSharedLibraries(sharedLibraries) {
|
|
228
|
+
const merged = { pages: {}, components: {}, functions: {}, methods: {}, snippets: {} }
|
|
229
|
+
for (const lib of libs) {
|
|
230
|
+
if (isPlainObject(lib?.pages)) Object.assign(merged.pages, lib.pages)
|
|
231
|
+
if (isPlainObject(lib?.components)) Object.assign(merged.components, lib.components)
|
|
232
|
+
// ...
|
|
233
|
+
}
|
|
234
|
+
return merged
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Context Preparation Sequence
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
prepareContext() {
|
|
244
|
+
1. Set key, define, cssPropsRegistry, window
|
|
245
|
+
|
|
246
|
+
2. prepareSharedLibs(context)
|
|
247
|
+
→ deepMerge each library into context
|
|
248
|
+
|
|
249
|
+
3. prepareDesignSystem(context)
|
|
250
|
+
→ uses context.designSystem (now includes shared lib data)
|
|
251
|
+
|
|
252
|
+
4. prepareState(app, context)
|
|
253
|
+
→ merges context.state + app.state
|
|
254
|
+
|
|
255
|
+
5. preparePages(app, context)
|
|
256
|
+
→ merges app.routes + context.pages
|
|
257
|
+
|
|
258
|
+
6. prepareComponents(context)
|
|
259
|
+
→ { ...UIkit, ...context.components }
|
|
260
|
+
|
|
261
|
+
7. prepareUtils(context)
|
|
262
|
+
→ spreads utils, router, snippets, functions
|
|
263
|
+
|
|
264
|
+
8. prepareDependencies()
|
|
265
|
+
|
|
266
|
+
9. prepareMethods(context)
|
|
267
|
+
→ spreads context.methods + require/router
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Editor Workspace Map
|
|
274
|
+
|
|
275
|
+
| Package | sharedLibraries | Role |
|
|
276
|
+
|---------|----------------|------|
|
|
277
|
+
| **brand** | `[]` | Provider — colors, typography, design system |
|
|
278
|
+
| **shared** | none | Provider — common components, functions |
|
|
279
|
+
| **preview** | `[brand, shared]` | Consumer — standalone preview app |
|
|
280
|
+
| **convert** | `[brand, shared]` | Consumer — code converter app |
|
|
281
|
+
| **inspect** | `[brand, cms, shared]` | Consumer — inspector editor |
|
|
282
|
+
| **cms** | `[brand, shared]` | Consumer — CMS editor |
|
|
283
|
+
| **docs** | `[shared]` | Consumer — documentation |
|
|
284
|
+
| **assistant** | `[platform, docs, brand, shared]` | Consumer — AI assistant (remote + local libs) |
|
|
285
|
+
| **no-code** | `[brand, cms]` | Consumer — no-code editor |
|
|
286
|
+
| **canvas** | `[]` | Consumer — canvas editor (no shared libs) |
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Key Takeaways
|
|
291
|
+
|
|
292
|
+
1. **No re-exports needed** — components/functions from a shared library are automatically available in the consuming app's context
|
|
293
|
+
2. **App always wins** — app's own definitions take precedence over shared libraries
|
|
294
|
+
3. **Order matters** — first library in the array has priority over later ones for filling undefined slots
|
|
295
|
+
4. **Templates are special** — designSystem is fully overwritten (shallow) rather than deep-merged
|
|
296
|
+
5. **Methods are protected** — METHODS_EXL prevents shared libraries from overwriting element methods and internal references
|
|
297
|
+
6. **No circular detection** — JavaScript's ESM module system prevents infinite loops naturally; circular deps resolve as undefined values
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Source Files
|
|
302
|
+
|
|
303
|
+
| Purpose | File | Lines |
|
|
304
|
+
|---------|------|-------|
|
|
305
|
+
| Runtime merge entry | `smbls/packages/smbls/src/createDomql.js` | 42-44 |
|
|
306
|
+
| prepareSharedLibs | `smbls/packages/smbls/src/prepare.js` | 240-251 |
|
|
307
|
+
| deepMerge | `smbls/packages/utils/object.js` | 61-76 |
|
|
308
|
+
| overwriteShallow | `smbls/packages/utils/object.js` | 431-439 |
|
|
309
|
+
| METHODS_EXL | `smbls/packages/utils/keys.js` | 147-152 |
|
|
310
|
+
| Config normalization | `smbls/packages/cli/helpers/symbolsConfig.js` | 246-268 |
|
|
311
|
+
| FS scaffolding | `smbls/packages/cli/bin/fs.js` | 231-345 |
|
|
312
|
+
| Context modules | `smbls/packages/frank/toJSON.js` | 170-183 |
|
|
313
|
+
| Validation merge | `smbls/packages/cli/bin/validate-domql-runner.js` | 323-342 |
|
|
@@ -19,9 +19,11 @@ export const MyCard = {
|
|
|
19
19
|
theme: 'dialog',
|
|
20
20
|
round: 'C',
|
|
21
21
|
|
|
22
|
-
// HTML attributes (
|
|
22
|
+
// HTML attributes (auto-detected by attrs-in-props)
|
|
23
23
|
role: 'region',
|
|
24
|
-
|
|
24
|
+
ariaLabel: 'My card', // camelCase → aria-label
|
|
25
|
+
aria: { describedby: 'desc' }, // object shorthand → aria-describedby
|
|
26
|
+
attr: { 'aria-label': ({ props }) => props.label }, // explicit attr block
|
|
25
27
|
|
|
26
28
|
// State
|
|
27
29
|
state: { open: false },
|
|
@@ -150,14 +152,25 @@ export const Hoverable = {
|
|
|
150
152
|
}
|
|
151
153
|
```
|
|
152
154
|
|
|
153
|
-
###
|
|
155
|
+
### Conditional Props (Cases)
|
|
156
|
+
|
|
157
|
+
Three prefix types for conditional CSS and attributes:
|
|
158
|
+
|
|
159
|
+
| Prefix | Resolution | Example |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `$` | Global case from `context.cases` | `$isSafari: { padding: 'B' }` |
|
|
162
|
+
| `.` | Props/state first, then `context.cases` | `.isActive: { opacity: 1 }` |
|
|
163
|
+
| `!` | Inverted — applies when falsy | `!isActive: { opacity: 0 }` |
|
|
164
|
+
|
|
165
|
+
Cases are defined in `symbols/cases.js` and added to `context.cases`. Both CSS props and HTML attributes inside conditional blocks are applied.
|
|
154
166
|
|
|
155
167
|
```js
|
|
156
168
|
export const Item = {
|
|
157
169
|
opacity: 0.6,
|
|
158
|
-
'.active': { opacity: 1, fontWeight: '600' },
|
|
159
|
-
'.disabled': { opacity: 0.3, pointerEvents: 'none' },
|
|
160
|
-
'
|
|
170
|
+
'.active': { opacity: 1, fontWeight: '600', aria: { selected: true } },
|
|
171
|
+
'.disabled': { opacity: 0.3, pointerEvents: 'none', disabled: true },
|
|
172
|
+
'!active': { ariaHidden: true },
|
|
173
|
+
'$isSafari': { padding: 'B' }
|
|
161
174
|
}
|
|
162
175
|
```
|
|
163
176
|
|