@symbo.ls/mcp 1.0.9 → 1.0.11
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/package.json +5 -2
- package/symbols_mcp/skills/COMPONENTS.md +421 -0
- package/symbols_mcp/skills/DESIGN_SYSTEM.md +430 -0
- package/symbols_mcp/skills/MIGRATION.md +520 -0
- package/symbols_mcp/skills/PATTERNS.md +509 -0
- package/symbols_mcp/skills/PROJECT_STRUCTURE.md +400 -0
- package/symbols_mcp/skills/RULES.md +488 -0
- package/symbols_mcp/skills/SEO-METADATA.md +41 -1
- package/symbols_mcp/skills/SYNTAX.md +777 -0
- package/symbols_mcp/skills/ACCESSIBILITY.md +0 -471
- package/symbols_mcp/skills/ACCESSIBILITY_AUDITORY.md +0 -70
- package/symbols_mcp/skills/AGENT_INSTRUCTIONS.md +0 -265
- package/symbols_mcp/skills/BUILT_IN_COMPONENTS.md +0 -304
- package/symbols_mcp/skills/CLAUDE.md +0 -2158
- package/symbols_mcp/skills/CLI_QUICK_START.md +0 -205
- package/symbols_mcp/skills/DEFAULT_COMPONENTS.md +0 -2002
- package/symbols_mcp/skills/DEFAULT_DESIGN_SYSTEM.md +0 -496
- package/symbols_mcp/skills/DESIGN_SYSTEM_CONFIG.md +0 -487
- package/symbols_mcp/skills/DESIGN_SYSTEM_IN_PROPS.md +0 -136
- package/symbols_mcp/skills/DOMQL_v2-v3_MIGRATION.md +0 -236
- package/symbols_mcp/skills/MIGRATE_TO_SYMBOLS.md +0 -634
- package/symbols_mcp/skills/OPTIMIZATIONS_FOR_AGENT.md +0 -253
- package/symbols_mcp/skills/PROJECT_SETUP.md +0 -217
- package/symbols_mcp/skills/QUICKSTART.md +0 -79
- package/symbols_mcp/skills/REMOTE_PREVIEW.md +0 -144
- package/symbols_mcp/skills/SYMBOLS_LOCAL_INSTRUCTIONS.md +0 -1405
- package/symbols_mcp/skills/UI_UX_PATTERNS.md +0 -68
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
# Symbols / DOMQL v3 — Strict Rules for AI Agents
|
|
2
|
+
|
|
3
|
+
You are working in a **Symbols.app** / DOMQL v3 project. These rules are absolute and override any general coding instincts. Violations cause silent failures (black page, nothing renders).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## CRITICAL: v3 Syntax Only
|
|
8
|
+
|
|
9
|
+
| v3 ✅ USE THIS | v2 ❌ NEVER USE |
|
|
10
|
+
| -------------------------- | -------------------------------- |
|
|
11
|
+
| `extends: 'Component'` | ~~`extend: 'Component'`~~ |
|
|
12
|
+
| `childExtends: 'Component'`| ~~`childExtend: 'Component'`~~ |
|
|
13
|
+
| `onClick: fn` | ~~`on: { click: fn }`~~ |
|
|
14
|
+
| `onRender: fn` | ~~`on: { render: fn }`~~ |
|
|
15
|
+
| props flattened at root | ~~`props: { ... }` wrapper~~ |
|
|
16
|
+
| individual prop functions | ~~`props: ({ state }) => ({})` function~~ |
|
|
17
|
+
| `flexAlign: 'center center'`| ~~`align: 'center center'`~~ |
|
|
18
|
+
| `children` + `childExtends`| ~~`$collection`, `$propsCollection`~~ |
|
|
19
|
+
| `children` + `childrenAs: 'state'`| ~~`$stateCollection`~~ |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Rule 1 — Components are OBJECTS, never functions
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// ✅ CORRECT
|
|
27
|
+
export const Header = { extends: 'Flex', padding: 'A' }
|
|
28
|
+
|
|
29
|
+
// ❌ WRONG — never do this
|
|
30
|
+
export const Header = (el, state) => ({ padding: 'A' })
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Rule 2 — NO imports between project files — EVER
|
|
36
|
+
|
|
37
|
+
Components reference each other by PascalCase key in the object tree. Never use `import` between `components/`, `pages/`, `functions/`, etc.
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
// ❌ WRONG
|
|
41
|
+
import { Navbar } from './Navbar.js'
|
|
42
|
+
|
|
43
|
+
// ✅ CORRECT — just use the key name in the tree
|
|
44
|
+
Nav: { extends: 'Navbar' }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Only exception**: `pages/index.js` is the route registry — imports ARE allowed there.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// pages/index.js — only file where imports are permitted
|
|
51
|
+
import { main } from './main.js'
|
|
52
|
+
export default { '/': main }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Rule 3 — `components/index.js` — use `export *` NOT `export * as`
|
|
58
|
+
|
|
59
|
+
`export * as Foo` wraps everything in a namespace object and **breaks component resolution entirely**.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
// ✅ CORRECT
|
|
63
|
+
export * from './Navbar.js'
|
|
64
|
+
export * from './PostCard.js'
|
|
65
|
+
|
|
66
|
+
// ❌ WRONG — breaks everything
|
|
67
|
+
export * as Navbar from './Navbar.js'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Rule 4 — Pages extend `'Page'`, not `'Flex'` or `'Box'`
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// ✅ CORRECT
|
|
76
|
+
export const main = { extends: 'Page', ... }
|
|
77
|
+
|
|
78
|
+
// ❌ WRONG
|
|
79
|
+
export const main = { extends: 'Flex', ... }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Rule 5 — All folders are flat — no subfolders
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
components/Navbar.js ✅
|
|
88
|
+
components/nav/Navbar.js ❌
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Rule 6 — PascalCase keys = child components (auto-extends)
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// The key "Hgroup" auto-extends the registered Hgroup component
|
|
97
|
+
export const MyCard = {
|
|
98
|
+
Hgroup: { // ← auto-extends: 'Hgroup' because key matches registered component
|
|
99
|
+
gap: '0', // ← override merges with base Hgroup
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
If you need a different base, set `extends` explicitly.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Rule 7 — State — use `s.update()`, never mutate directly
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
onClick: (e, el, s) => s.update({ count: s.count + 1 }) // ✅ CORRECT
|
|
112
|
+
onClick: (e, el, s) => { s.count = s.count + 1 } // ❌ WRONG — no re-render
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Root-level global state: `s.root.update({ key: val })`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Rule 8 — `el.call('fn', arg)` — `this` is the element inside the function
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
// functions/myFn.js
|
|
123
|
+
export const myFn = function myFn(arg1) {
|
|
124
|
+
const node = this.node // 'this' is the DOMQL element
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onClick: (e, el) => el.call('myFn', someArg) // ✅ CORRECT
|
|
128
|
+
onClick: (e, el) => el.call('myFn', el, someArg) // ❌ WRONG — el passed twice
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Rule 9 — Icons: NEVER use `Icon` inside `Button` — use `Svg` atom
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
// ✅ CORRECT
|
|
137
|
+
MyBtn: {
|
|
138
|
+
extends: 'Flex', tag: 'button', flexAlign: 'center center', cursor: 'pointer',
|
|
139
|
+
Svg: { viewBox: '0 0 24 24', width: '22', height: '22',
|
|
140
|
+
html: '<path d="..." fill="currentColor"/>' }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ❌ WRONG — Icon will NOT render inside Button
|
|
144
|
+
MyBtn: { extends: 'Button', Icon: { name: 'heart' } }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Rule 10 — `childExtends` MUST be a named component string, never an inline object
|
|
150
|
+
|
|
151
|
+
Inline `childExtends` objects cause ALL property values to be concatenated as visible text content on every child.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
// ✅ CORRECT — reference a named component
|
|
155
|
+
childExtends: 'NavLink'
|
|
156
|
+
|
|
157
|
+
// ❌ WRONG — dumps all prop values as raw text on every child
|
|
158
|
+
childExtends: {
|
|
159
|
+
tag: 'button',
|
|
160
|
+
background: 'transparent', // renders as visible text!
|
|
161
|
+
border: '2px solid transparent'
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Define shared styles as a named component in `components/`, register in `components/index.js`, then reference by name.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Rule 11 — Color token syntax (dot-notation)
|
|
170
|
+
|
|
171
|
+
Color tokens use **dot-notation** for opacity and `+`/`-`/`=` for tone modifiers:
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
// ✅ CORRECT — dot-notation opacity
|
|
175
|
+
{ color: 'white.7' }
|
|
176
|
+
{ background: 'black.5' }
|
|
177
|
+
{ background: 'gray.92+8' } // opacity 0.92, tone +8
|
|
178
|
+
{ color: 'gray+16' } // full opacity, tone +16
|
|
179
|
+
{ color: 'gray=90' } // absolute lightness 90%
|
|
180
|
+
|
|
181
|
+
// ❌ WRONG — old space-separated syntax (no longer supported)
|
|
182
|
+
{ color: 'white .7' }
|
|
183
|
+
{ color: 'gray 1 +16' }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
For rarely-used colors, define named tokens in `designSystem/COLOR.js` instead.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Rule 12 — Border, boxShadow, textShadow — space-separated (CSS-like)
|
|
191
|
+
|
|
192
|
+
These properties use **space-separated** syntax matching CSS conventions:
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
// ✅ CORRECT — space-separated, CSS order
|
|
196
|
+
{ border: '1px solid gray.1' }
|
|
197
|
+
{ border: 'solid mediumGrey' }
|
|
198
|
+
{ boxShadow: 'black.1 0 A C C' }
|
|
199
|
+
{ textShadow: 'gray1 6px 6px' }
|
|
200
|
+
|
|
201
|
+
// Multiple shadows use commas (CSS standard)
|
|
202
|
+
{ boxShadow: 'black.1 0 A C C, white.5 0 B D D' }
|
|
203
|
+
|
|
204
|
+
// Raw CSS passes through unchanged
|
|
205
|
+
{ boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }
|
|
206
|
+
|
|
207
|
+
// ❌ WRONG — old comma-separated syntax (no longer supported)
|
|
208
|
+
{ border: 'solid, gray, 1px' }
|
|
209
|
+
{ boxShadow: 'black .1, 0, A, C, C' }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Rule 13 — CSS override precedence — component level beats props level
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
// ✅ CORRECT — override at component level to match base component level
|
|
218
|
+
export const MyLink = {
|
|
219
|
+
extends: 'Link',
|
|
220
|
+
color: 'mediumBlue', // WINS — same declaration level
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ❌ WRONG — props block CANNOT override component-level CSS
|
|
224
|
+
export const MyLink = {
|
|
225
|
+
extends: 'Link',
|
|
226
|
+
props: { color: 'mediumBlue' } // LOSES to Link's component-level color
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Rule 14 — Dynamic HTML attributes go in `attr: {}`, not at root level
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
// ✅ CORRECT — attr block resolves functions
|
|
236
|
+
export const Input = {
|
|
237
|
+
type: 'text', // static default at root
|
|
238
|
+
attr: {
|
|
239
|
+
type: ({ props }) => props.type // dynamic resolution in attr
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ❌ WRONG — function at component level gets stringified into the HTML attribute
|
|
244
|
+
export const Input = {
|
|
245
|
+
type: ({ props }) => props.type // renders as: type="(el)=>el.props.type"
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Rule 15 — `onRender` — guard against double-init
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
onRender: (el) => {
|
|
255
|
+
if (el.__initialized) return
|
|
256
|
+
el.__initialized = true
|
|
257
|
+
// imperative logic here
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Rule 16 — SVGs belong in `designSystem/svg_data.js`, not inline in components
|
|
264
|
+
|
|
265
|
+
Store SVG markup in the design system and reference via context:
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
// designSystem/svg_data.js
|
|
269
|
+
export default {
|
|
270
|
+
folderTopRight: '<svg ...>...</svg>',
|
|
271
|
+
folderBottomLeft: '<svg ...>...</svg>',
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// In component — reference from designSystem
|
|
275
|
+
Svg: {
|
|
276
|
+
src: ({ context }) => context.designSystem.SVG_DATA && context.designSystem.SVG_DATA.folderTopRight,
|
|
277
|
+
aspectRatio: '466 / 48',
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ❌ WRONG — inline SVG string in component
|
|
281
|
+
Svg: {
|
|
282
|
+
src: '<svg fill="none" viewBox="0 0 466 48">...</svg>',
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Rule 17 — `customRouterElement` for persistent layouts
|
|
289
|
+
|
|
290
|
+
Use `customRouterElement` in config to render pages inside a specific element instead of the root:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
// config.js
|
|
294
|
+
export default {
|
|
295
|
+
router: {
|
|
296
|
+
customRouterElement: 'Folder.Content' // dot-separated path from root
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The `/` (main) page defines the persistent layout. Sub-pages are rendered inside the target element without re-creating the layout.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Rule 18a — Tab/view switching — use DOM IDs + function, NOT reactive `display`
|
|
306
|
+
|
|
307
|
+
Reactive `display: (el, s) => ...` on multiple full-page trees causes rendering failures.
|
|
308
|
+
|
|
309
|
+
```js
|
|
310
|
+
// ✅ CORRECT — use DOM ID pattern
|
|
311
|
+
HomeView: { id: 'view-home', extends: 'Flex', ... },
|
|
312
|
+
ExploreView: { id: 'view-explore', extends: 'Flex', display: 'none', ... },
|
|
313
|
+
|
|
314
|
+
// functions/switchView.js
|
|
315
|
+
export const switchView = function switchView(view) {
|
|
316
|
+
['home', 'explore'].forEach(v => {
|
|
317
|
+
const el = document.getElementById('view-' + v)
|
|
318
|
+
if (el) el.style.display = v === view ? 'flex' : 'none'
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Rule 17 — v3 Conditional Props — use `isX` + `'.isX'`
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
// ✅ NEW — v3 conditional props pattern
|
|
329
|
+
export const ModalCard = {
|
|
330
|
+
opacity: '0',
|
|
331
|
+
visibility: 'hidden',
|
|
332
|
+
|
|
333
|
+
isActive: (el, s) => s.root.activeModal,
|
|
334
|
+
'.isActive': {
|
|
335
|
+
opacity: '1',
|
|
336
|
+
visibility: 'visible',
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ❌ OLD — props function with conditional spread (deprecated)
|
|
341
|
+
export const ModalCard = {
|
|
342
|
+
props: (el, s) => ({
|
|
343
|
+
...(s.root.activeModal ? { opacity: '1' } : { opacity: '0' })
|
|
344
|
+
}),
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Rule 18 — CSS transitions require forced reflow
|
|
351
|
+
|
|
352
|
+
DOMQL + Emotion apply all CSS changes in one JS tick. The browser skips the "before" state. Fix:
|
|
353
|
+
|
|
354
|
+
```js
|
|
355
|
+
// FadeIn pattern
|
|
356
|
+
modalNode.style.opacity = '0'
|
|
357
|
+
modalNode.style.visibility = 'visible'
|
|
358
|
+
state.root.update({ activeModal: true }, { onlyUpdate: 'ModalCard' })
|
|
359
|
+
modalNode.style.opacity = '0'
|
|
360
|
+
modalNode.offsetHeight // forces reflow — browser paints opacity:0
|
|
361
|
+
modalNode.style.opacity = '' // releases — Emotion class opacity:1 triggers transition
|
|
362
|
+
|
|
363
|
+
// FadeOut pattern
|
|
364
|
+
modalNode.style.opacity = '0'
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
modalNode.style.opacity = ''
|
|
367
|
+
modalNode.style.visibility = ''
|
|
368
|
+
state.root.update({ activeModal: false }, { onlyUpdate: 'ModalCard' })
|
|
369
|
+
}, 280) // match CSS transition duration
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Rule 19 — Semantic-First Architecture
|
|
375
|
+
|
|
376
|
+
Use semantic components, never generic divs for meaningful content:
|
|
377
|
+
|
|
378
|
+
| Intent | Use |
|
|
379
|
+
| ------------------------- | ---------------- |
|
|
380
|
+
| Page header | Header |
|
|
381
|
+
| Navigation | Nav |
|
|
382
|
+
| Primary content | Main |
|
|
383
|
+
| Standalone article/entity | Article |
|
|
384
|
+
| Thematic grouping | Section |
|
|
385
|
+
| Sidebar | Aside |
|
|
386
|
+
| Actions | Button |
|
|
387
|
+
| Navigation links | Link |
|
|
388
|
+
| User input | Input / Form |
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Rule 20 — ARIA and accessibility attributes
|
|
393
|
+
|
|
394
|
+
```js
|
|
395
|
+
attr: {
|
|
396
|
+
role: 'button',
|
|
397
|
+
tabindex: '0',
|
|
398
|
+
'aria-label': ({ props }) => props.label,
|
|
399
|
+
'aria-busy': ({ state }) => state.loading,
|
|
400
|
+
'aria-live': 'polite'
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Use native elements instead of role overrides wherever possible.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Project structure quick reference
|
|
409
|
+
|
|
410
|
+
```
|
|
411
|
+
smbls/
|
|
412
|
+
├── index.js # export * as components, export default pages, etc.
|
|
413
|
+
├── state.js # export default { ... }
|
|
414
|
+
├── dependencies.js # export default { 'pkg': 'version' }
|
|
415
|
+
├── components/
|
|
416
|
+
│ ├── index.js # export * from './Foo.js' ← flat re-exports ONLY
|
|
417
|
+
│ └── Navbar.js # export const Navbar = { ... }
|
|
418
|
+
├── pages/
|
|
419
|
+
│ ├── index.js # import + export default { '/': main }
|
|
420
|
+
│ └── main.js # export const main = { extends: 'Page', ... }
|
|
421
|
+
├── functions/
|
|
422
|
+
│ ├── index.js # export * from './switchView.js'
|
|
423
|
+
│ └── switchView.js # export const switchView = function(...) {}
|
|
424
|
+
└── designSystem/
|
|
425
|
+
└── index.js # export default { COLOR, THEME, ... }
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Rule 21 — Picture `src` goes on Img child, never on Picture
|
|
431
|
+
|
|
432
|
+
The HTML `<picture>` tag does NOT support `src` as an attribute. In v3, lowercase props move to `element.props`, so `element.parent.src` returns `undefined`.
|
|
433
|
+
|
|
434
|
+
```js
|
|
435
|
+
// ✅ CORRECT — src on the Img child
|
|
436
|
+
Picture: {
|
|
437
|
+
Img: { src: '/files/photo.jpg' },
|
|
438
|
+
width: '100%',
|
|
439
|
+
aspectRatio: '16/9'
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ❌ WRONG — src on Picture is silently ignored
|
|
443
|
+
Picture: { src: '/files/photo.jpg', width: '100%' }
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Rule 22 — Component key `Map` auto-detects as `<map>` — add `tag: 'div'`
|
|
449
|
+
|
|
450
|
+
The key `Map` is auto-detected as the HTML `<map>` element (for image maps), which defaults to `display: inline` and has height 0. Always add `tag: 'div'` when using `Map` as a component name:
|
|
451
|
+
|
|
452
|
+
```js
|
|
453
|
+
export const Map = {
|
|
454
|
+
extends: 'Flex',
|
|
455
|
+
tag: 'div', // prevents <map> auto-detection
|
|
456
|
+
// ...
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## Rule 23 — `/files/` path resolution
|
|
463
|
+
|
|
464
|
+
File paths like `/files/logo.png` reference the framework's embedded file system via `context.files`. The `/files/` prefix is stripped automatically when resolving — keys are just filenames (e.g., `"logo.png"`, not `"/files/logo.png"`).
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Output Verification Checklist
|
|
469
|
+
|
|
470
|
+
Before finalizing any generated code, verify:
|
|
471
|
+
|
|
472
|
+
- [ ] Components are objects, not functions
|
|
473
|
+
- [ ] No cross-file imports except `pages/index.js`
|
|
474
|
+
- [ ] `components/index.js` uses `export *`, not `export * as`
|
|
475
|
+
- [ ] All folders are flat (no subfolders)
|
|
476
|
+
- [ ] Pages extend `'Page'`
|
|
477
|
+
- [ ] v3 event syntax (`onClick`, `onRender`, not `on: { click: ... }`)
|
|
478
|
+
- [ ] `flexAlign` not `align` for Flex shorthand
|
|
479
|
+
- [ ] State updated via `state.update()`, never direct mutation
|
|
480
|
+
- [ ] `childExtends` references named component strings only
|
|
481
|
+
- [ ] No opacity modifier syntax in color props (`color: 'white .7'` is invalid)
|
|
482
|
+
- [ ] Dynamic HTML attributes are in `attr: {}` block, not at root level
|
|
483
|
+
- [ ] One H1 per page; logical heading hierarchy H1→H2→H3
|
|
484
|
+
- [ ] Buttons for actions, Links for navigation
|
|
485
|
+
- [ ] Forms have labeled inputs with `name` and `type` attributes
|
|
486
|
+
- [ ] Picture `src` is on the Img child, not on Picture itself
|
|
487
|
+
- [ ] `Map` component key has `tag: 'div'` to avoid `<map>` auto-detection
|
|
488
|
+
- [ ] `$propsCollection` / `$stateCollection` replaced with `children` pattern
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SEO Metadata
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Symbols provides comprehensive SEO metadata support through the `@symbo.ls/helmet` plugin. Define a declarative `metadata` object on your app, pages, or any component. The same code works at runtime (updates DOM `<head>` tags) and during SSR (generates HTML via brender).
|
|
4
4
|
|
|
5
5
|
The system automatically:
|
|
6
6
|
|
|
@@ -8,6 +8,8 @@ The system automatically:
|
|
|
8
8
|
- Expands array values into multiple tags
|
|
9
9
|
- Handles namespace prefixes (`og:`, `twitter:`, `article:`, `product:`, `DC:`, `itemprop:`, `http-equiv:`)
|
|
10
10
|
- Outputs valid HTML head markup
|
|
11
|
+
- Supports function values receiving `(element, state)` for dynamic metadata
|
|
12
|
+
- Merges metadata from global SEO settings, app-level, and page-level (page wins)
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
@@ -108,3 +110,41 @@ export default {
|
|
|
108
110
|
},
|
|
109
111
|
};
|
|
110
112
|
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
# Dynamic Metadata
|
|
117
|
+
|
|
118
|
+
Metadata values can be functions receiving `(element, state)`. Both the whole object and individual properties support this:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
// Whole metadata as function
|
|
122
|
+
export const productPage = {
|
|
123
|
+
metadata: (el, s) => ({
|
|
124
|
+
title: s.product.name + ' — My Store',
|
|
125
|
+
description: s.product.description,
|
|
126
|
+
'og:image': s.product.image
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Individual properties as functions
|
|
131
|
+
export const profilePage = {
|
|
132
|
+
metadata: {
|
|
133
|
+
title: (el, s) => `${s.user.name} — My App`,
|
|
134
|
+
description: (el, s) => s.user.bio,
|
|
135
|
+
'og:image': '/default-avatar.png' // static fallback
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
# Merge Priority
|
|
143
|
+
|
|
144
|
+
When metadata is defined at multiple levels, higher priority wins:
|
|
145
|
+
|
|
146
|
+
1. `data.integrations.seo` — global SEO settings (lowest)
|
|
147
|
+
2. `data.app.metadata` — app-level defaults
|
|
148
|
+
3. `page.metadata` — page-level overrides (highest)
|
|
149
|
+
|
|
150
|
+
If no `title` is found after merging, it falls back to `page.state.title`, then `data.name`.
|