@symbo.ls/mcp 1.0.10 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +1 -0
  2. package/package.json +5 -2
  3. package/symbols_mcp/skills/AUDIT.md +148 -174
  4. package/symbols_mcp/skills/BRAND_IDENTITY.md +75 -0
  5. package/symbols_mcp/skills/COMPONENTS.md +266 -0
  6. package/symbols_mcp/skills/COOKBOOK.md +850 -0
  7. package/symbols_mcp/skills/DEFAULT_COMPONENTS.md +3491 -1637
  8. package/symbols_mcp/skills/DEFAULT_LIBRARY.md +301 -0
  9. package/symbols_mcp/skills/DESIGN_CRITIQUE.md +70 -59
  10. package/symbols_mcp/skills/DESIGN_DIRECTION.md +109 -175
  11. package/symbols_mcp/skills/DESIGN_SYSTEM.md +722 -0
  12. package/symbols_mcp/skills/DESIGN_SYSTEM_ARCHITECT.md +65 -57
  13. package/symbols_mcp/skills/DESIGN_TO_CODE.md +83 -64
  14. package/symbols_mcp/skills/DESIGN_TREND.md +62 -50
  15. package/symbols_mcp/skills/FIGMA_MATCHING.md +69 -58
  16. package/symbols_mcp/skills/LEARNINGS.md +374 -0
  17. package/symbols_mcp/skills/MARKETING_ASSETS.md +71 -59
  18. package/symbols_mcp/skills/MIGRATION.md +561 -0
  19. package/symbols_mcp/skills/PATTERNS.md +536 -0
  20. package/symbols_mcp/skills/PRESENTATION.md +78 -0
  21. package/symbols_mcp/skills/PROJECT_STRUCTURE.md +398 -0
  22. package/symbols_mcp/skills/RULES.md +519 -0
  23. package/symbols_mcp/skills/RUNNING_APPS.md +476 -0
  24. package/symbols_mcp/skills/SEO-METADATA.md +64 -9
  25. package/symbols_mcp/skills/SNIPPETS.md +598 -0
  26. package/symbols_mcp/skills/SSR-BRENDER.md +99 -0
  27. package/symbols_mcp/skills/SYNTAX.md +835 -0
  28. package/symbols_mcp/skills/ACCESSIBILITY.md +0 -471
  29. package/symbols_mcp/skills/ACCESSIBILITY_AUDITORY.md +0 -70
  30. package/symbols_mcp/skills/AGENT_INSTRUCTIONS.md +0 -265
  31. package/symbols_mcp/skills/BRAND_INDENTITY.md +0 -69
  32. package/symbols_mcp/skills/BUILT_IN_COMPONENTS.md +0 -304
  33. package/symbols_mcp/skills/CLAUDE.md +0 -2158
  34. package/symbols_mcp/skills/CLI_QUICK_START.md +0 -205
  35. package/symbols_mcp/skills/DEFAULT_DESIGN_SYSTEM.md +0 -496
  36. package/symbols_mcp/skills/DESIGN_SYSTEM_CONFIG.md +0 -487
  37. package/symbols_mcp/skills/DESIGN_SYSTEM_IN_PROPS.md +0 -136
  38. package/symbols_mcp/skills/DOMQL_v2-v3_MIGRATION.md +0 -236
  39. package/symbols_mcp/skills/MIGRATE_TO_SYMBOLS.md +0 -634
  40. package/symbols_mcp/skills/OPTIMIZATIONS_FOR_AGENT.md +0 -253
  41. package/symbols_mcp/skills/PROJECT_SETUP.md +0 -217
  42. package/symbols_mcp/skills/QUICKSTART.md +0 -79
  43. package/symbols_mcp/skills/REMOTE_PREVIEW.md +0 -144
  44. package/symbols_mcp/skills/SYMBOLS_LOCAL_INSTRUCTIONS.md +0 -1405
  45. package/symbols_mcp/skills/THE_PRESENTATION.md +0 -69
  46. package/symbols_mcp/skills/UI_UX_PATTERNS.md +0 -68
@@ -0,0 +1,835 @@
1
+ # DOMQL v3 Syntax Reference
2
+
3
+ Authoritative reference for generating correct DOMQL v3 code. Every pattern is derived from real working code.
4
+
5
+ ---
6
+
7
+ ## Element Anatomy
8
+
9
+ Produce DOMQL elements as plain JS objects. Every key has a specific role:
10
+
11
+ ```js
12
+ export const MyCard = {
13
+ tag: 'section', // HTML tag (default: div)
14
+
15
+ // CSS props (top-level, promoted via propertizeElement)
16
+ padding: 'B C',
17
+ gap: 'A',
18
+ flow: 'column',
19
+ theme: 'dialog',
20
+ round: 'C',
21
+
22
+ // HTML attributes
23
+ attr: { role: 'region', 'aria-label': ({ props }) => props.label },
24
+
25
+ // State
26
+ state: { open: false },
27
+
28
+ // Events (v3 top-level)
29
+ onClick: (event, el, state) => { state.update({ open: !state.open }) },
30
+ onRender: (el, state) => { console.log('rendered') },
31
+
32
+ // Children (PascalCase keys)
33
+ Header: { text: ({ props }) => props.title },
34
+ Body: { html: ({ props }) => props.content }
35
+ }
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Element Lifecycle
41
+
42
+ ```
43
+ create(props, parent, key, options)
44
+ |- createElement() creates bare element
45
+ |- applyExtends() merges extends stack (element wins)
46
+ |- propertizeElement() routes onXxx events, promotes CSS props
47
+ |- addMethods() attaches el.lookup / el.update / etc.
48
+ |- initProps() builds props from propsStack
49
+ +- createNode()
50
+ |- throughInitialExec() executes function props
51
+ |- applyEventsOnNode() binds element.on.* to DOM
52
+ +- iterates children -> create() recursively
53
+ ```
54
+
55
+ `propertizeElement` runs BEFORE `addMethods`. Do not rely on prototype methods during propertization.
56
+
57
+ ### Propertization and Define System
58
+
59
+ `propertizeElement()` classifies keys between root and `props`. Keys starting with `$` overlap between css-in-props conditionals (`$isActive`) and define handlers (`$router`, deprecated v2 handlers like `$propsCollection`, `$collection`).
60
+
61
+ Define handlers (`element.define[key]` or `context.define[key]`) must stay at the element root so `throughInitialDefine` can process them. Propertization checks for define handlers BEFORE applying `CSS_SELECTOR_PREFIXES`:
62
+
63
+ ```js
64
+ const defineValue = this.define?.[key]
65
+ const globalDefineValue = this.context?.define?.[key]
66
+ if (isFunction(defineValue) || isFunction(globalDefineValue)) continue
67
+ if (CSS_SELECTOR_PREFIXES.has(firstChar)) {
68
+ obj.props[key] = value // move to props for css-in-props
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## REGISTRY Keys
75
+
76
+ These keys are handled by DOMQL internally and are NOT promoted to CSS props:
77
+
78
+ ```
79
+ attr, style, text, html, data, classlist, state, scope, deps,
80
+ extends, children, content,
81
+ childExtend (deprecated), childExtends, childExtendRecursive (deprecated), childExtendsRecursive,
82
+ props, if, define, __name, __ref, __hash, __text,
83
+ key, tag, query, parent, node, variables, on, component, context
84
+ ```
85
+
86
+ Any key NOT in this list and not PascalCase (component) is promoted to `element.props` as a CSS prop.
87
+
88
+ Always use `childExtends` (plural). The singular `childExtend` is deprecated v2 syntax kept for backwards compatibility.
89
+
90
+ ---
91
+
92
+ ## Extending and Composing
93
+
94
+ | Pattern | Syntax |
95
+ |---|---|
96
+ | Single extend | `extends: 'Button'` |
97
+ | Multiple (first = highest priority) | `extends: [Link, RouterLink]` |
98
+ | String reference (from `context.components`) | `extends: 'Hoverable'` |
99
+ | Multiple strings | `extends: ['IconText', 'FocusableComponent']` |
100
+
101
+ ### Merge Semantics
102
+
103
+ | Type | Rule |
104
+ |---|---|
105
+ | Own properties | Always win over extends |
106
+ | Objects | Deep-merged (both sides preserved) |
107
+ | Functions | NOT merged; element's function replaces extend's |
108
+ | Arrays | Concatenated |
109
+
110
+ ---
111
+
112
+ ## CSS Props (Top-Level Promotion)
113
+
114
+ Place CSS props at the element root. Non-registry, non-PascalCase keys become `element.props`:
115
+
116
+ ```js
117
+ export const Card = {
118
+ padding: 'B C', // -> props.padding
119
+ gap: 'Z', // -> props.gap
120
+ flow: 'column', // shorthand for flexDirection
121
+ align: 'center', // NOT flexAlign
122
+ fontSize: 'A',
123
+ fontWeight: '500',
124
+ color: 'currentColor',
125
+ background: 'codGray',
126
+ round: 'C', // border-radius token
127
+ opacity: '0.85',
128
+ overflow: 'hidden',
129
+ transition: 'B defaultBezier',
130
+ transitionProperty: 'opacity, transform',
131
+ zIndex: 10,
132
+ tag: 'section', // stays at root (REGISTRY)
133
+ attr: { href: '...' } // stays at root (REGISTRY)
134
+ }
135
+ ```
136
+
137
+ ### Pseudo-Classes and Pseudo-Elements
138
+
139
+ ```js
140
+ export const Hoverable = {
141
+ opacity: 0.85,
142
+ ':hover': { opacity: 0.9, transform: 'scale(1.015)' },
143
+ ':active': { opacity: 1, transform: 'scale(1.015)' },
144
+ ':focus-visible': { outline: 'solid X blue.3' },
145
+ ':not(:first-child)': {
146
+ '@dark': { borderWidth: '1px 0 0' },
147
+ '@light': { borderWidth: '1px 0 0' }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ### CSS Class State Modifiers (Emotion `.className`)
153
+
154
+ ```js
155
+ export const Item = {
156
+ opacity: 0.6,
157
+ '.active': { opacity: 1, fontWeight: '600' },
158
+ '.disabled': { opacity: 0.3, pointerEvents: 'none' },
159
+ '.hidden': { transform: 'translate3d(0,10%,0)', opacity: 0, visibility: 'hidden' }
160
+ }
161
+ ```
162
+
163
+ ### Raw Style Object (Escape Hatch)
164
+
165
+ ```js
166
+ export const DropdownParent = {
167
+ style: {
168
+ '&:hover': {
169
+ zIndex: 1000,
170
+ '& [dropdown]': { transform: 'translate3d(0,0,0)', opacity: 1 }
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ ### Media Queries
177
+
178
+ ```js
179
+ export const Grid = {
180
+ columns: 'repeat(4, 1fr)',
181
+ '@tabletSm': { columns: 'repeat(2, 1fr)' },
182
+ '@mobileL': { columns: '1fr' },
183
+ '@dark': { background: 'codGray' },
184
+ '@light': { background: 'concrete' }
185
+ }
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Events
191
+
192
+ ### v3 Syntax (Top-Level `onXxx`)
193
+
194
+ Use top-level `onXxx` handlers. Two signatures exist:
195
+
196
+ **DOM events** -- signature: `(event, el, state)`:
197
+
198
+ ```js
199
+ onClick: (event, el, state) => { /* ... */ }
200
+ onChange: (event, el, state) => { /* ... */ }
201
+ onInput: (event, el, state) => { state.update({ value: event.target.value }) }
202
+ onSubmit: (event, el, state) => { event.preventDefault() }
203
+ onKeydown: (event, el, state) => { if (event.key === 'Enter') /* ... */ }
204
+ onMouseover: (event, el, state) => { /* ... */ }
205
+ onBlur: (event, el, state) => { /* ... */ }
206
+ onFocus: (event, el, state) => { /* ... */ }
207
+ ```
208
+
209
+ **Lifecycle events** -- signature: `(el, state)`:
210
+
211
+ ```js
212
+ onInit: (el, state) => { /* before render */ }
213
+ onRender: (el, state) => { /* after render */ }
214
+ onCreate: (el, state) => { /* after full creation */ }
215
+ onUpdate: (el, state) => { /* after state/props update */ }
216
+ onStateUpdate: (el, state) => { /* after state update */ }
217
+ ```
218
+
219
+ ### Element Lifecycle Events (Full Signatures)
220
+
221
+ | Event | Signature | When | Notes |
222
+ |---|---|---|---|
223
+ | `onInit` | `(element, state, context, updateOptions)` | Before init | Return `false` to break |
224
+ | `onAttachNode` | `(element, state, context, updateOptions)` | After DOM node attached | |
225
+ | `onRender` | `(element, state, context, updateOptions)` | After render | |
226
+ | `onComplete` | `(element, state, context, updateOptions)` | After full creation | |
227
+ | `onBeforeUpdate` | `(changes, element, state, context, updateOptions)` | Before update | `changes` is first param |
228
+ | `onUpdate` | `(element, state, context, updateOptions)` | After update | |
229
+
230
+ ### State Events
231
+
232
+ | Event | Signature | When | Notes |
233
+ |---|---|---|---|
234
+ | `onStateInit` | `(element, state, context, updateOptions)` | Before state init | Return `false` to break |
235
+ | `onStateCreated` | `(element, state, context, updateOptions)` | After state created | |
236
+ | `onBeforeStateUpdate` | `(changes, element, state, context, updateOptions)` | Before state update | Return `false` to prevent; `changes` first |
237
+ | `onStateUpdate` | `(changes, element, state, context, updateOptions)` | After state update | `changes` is first param |
238
+
239
+ `onBeforeStateUpdate` and `onStateUpdate` receive `changes` as their FIRST parameter.
240
+
241
+ ### DOMQL Lifecycle Names (Never Bound to DOM)
242
+
243
+ ```
244
+ init, beforeClassAssign, render, renderRouter, attachNode,
245
+ stateInit, stateCreated, beforeStateUpdate, stateUpdate,
246
+ beforeUpdate, done, create, complete, frame, update
247
+ ```
248
+
249
+ ### Event Detection Rule
250
+
251
+ A key is a v3 event handler when:
252
+
253
+ ```js
254
+ key.length > 2 &&
255
+ key.startsWith('on') &&
256
+ key[2] === key[2].toUpperCase() && // onClick, onRender -- NOT "one", "only"
257
+ isFunction(value)
258
+ ```
259
+
260
+ ### Async Events
261
+
262
+ ```js
263
+ onRender: async (el, state) => {
264
+ try {
265
+ const result = await el.call('fetchData', el.props.id)
266
+ state.update({ data: result })
267
+ } catch (e) {
268
+ state.update({ error: e.message })
269
+ }
270
+ }
271
+ ```
272
+
273
+ ---
274
+
275
+ ## State
276
+
277
+ ### Define, Read, Update
278
+
279
+ ```js
280
+ // Define
281
+ state: { count: 0, open: false, selected: null }
282
+
283
+ // Read in definitions
284
+ text: ({ state }) => state.label
285
+ opacity: ({ state }) => state.loading ? 0.5 : 1
286
+ isActive: ({ key, state }) => state.active === key
287
+
288
+ // Update from events
289
+ onClick: (event, el, state) => {
290
+ state.update({ on: !state.on }) // partial update
291
+ state.set({ on: false }) // replace
292
+ state.reset() // reset to initial
293
+ state.toggle('open') // toggle boolean
294
+ }
295
+ ```
296
+
297
+ ### Root State Access
298
+
299
+ ```js
300
+ // From events
301
+ const rootState = el.getRootState()
302
+ const user = el.getRootState('user')
303
+
304
+ // In definitions
305
+ text: (el) => el.getRootState('currentPage')
306
+ ```
307
+
308
+ ### Targeted Updates (Performance)
309
+
310
+ ```js
311
+ state.root.update({ activeModal: true }, {
312
+ onlyUpdate: 'ModalCard' // only ModalCard subtree re-renders
313
+ })
314
+ ```
315
+
316
+ ---
317
+
318
+ ## State Methods
319
+
320
+ | Method | Description |
321
+ |---|---|
322
+ | `state.update(value, options?)` | Deep overwrite, triggers re-render |
323
+ | `state.set(value, options?)` | Replace state entirely (removes old values) |
324
+ | `state.reset(options?)` | Reset to initial values |
325
+ | `state.add(value, options?)` | Add item to array state |
326
+ | `state.toggle(key, options?)` | Toggle boolean property |
327
+ | `state.remove(key, options?)` | Remove property |
328
+ | `state.apply(fn, options?)` | Apply fn that RETURNS new value |
329
+ | `state.applyFunction(fn, options?)` | Apply fn that MUTATES state directly |
330
+ | `state.replace(value, options?)` | SHALLOW replace (nested keys disappear) |
331
+ | `state.clean(options?)` | Empty the state |
332
+ | `state.parse()` | Get purified plain object |
333
+ | `state.quietUpdate(value)` | Update without triggering re-render |
334
+ | `state.quietReplace(value)` | Replace without triggering re-render |
335
+ | `state.destroy(options?)` | Completely remove state |
336
+ | `state.setByPath('a.b.c', value)` | Update nested by dot-path |
337
+
338
+ `apply()` expects the function to RETURN a new value. `applyFunction()` expects direct MUTATION:
339
+
340
+ ```js
341
+ state.apply(s => ({ ...s, count: s.count + 1 })) // return
342
+ state.applyFunction(s => { s.count++ }) // mutate
343
+ ```
344
+
345
+ ### State Update Options
346
+
347
+ | Option | Description |
348
+ |---|---|
349
+ | `isHoisted` | Mark update as hoisted |
350
+ | `preventHoistElementUpdate` | Prevent hoisted element from updating |
351
+
352
+ ### State Navigation
353
+
354
+ ```js
355
+ state.parent // parent element's state
356
+ state.root // application-level root state
357
+ ```
358
+
359
+ State as string inherits from parent:
360
+
361
+ ```js
362
+ // Parent has state: { userProfile: { name: 'John' } }
363
+ state: 'userProfile' // child inherits parent's userProfile key
364
+ ```
365
+
366
+ ---
367
+
368
+ ## `attr` (HTML Attributes)
369
+
370
+ ```js
371
+ export const Input = {
372
+ tag: 'input',
373
+ attr: {
374
+ type: 'text',
375
+ autocomplete: 'off',
376
+ placeholder: ({ props }) => props.placeholder,
377
+ name: ({ props }) => props.name,
378
+ disabled: ({ props }) => props.disabled || null, // null removes attr
379
+ value: (el) => el.call('exec', el.props.value, el),
380
+ required: ({ props }) => props.required,
381
+ role: 'button',
382
+ 'aria-label': ({ props }) => props.aria?.label || props.text,
383
+ tabIndex: ({ props }) => props.tabIndex
384
+ }
385
+ }
386
+ ```
387
+
388
+ Return `null` or `undefined` from an attr function to remove the attribute.
389
+
390
+ ---
391
+
392
+ ## `text` and `html`
393
+
394
+ ```js
395
+ export const Label = { text: ({ props }) => props.label }
396
+ export const Badge = { text: 'New' }
397
+ export const Price = { text: ({ state }) => `$${state.amount.toFixed(2)}` }
398
+ export const RichText = { html: ({ props }) => props.html } // XSS risk
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Children
404
+
405
+ ### Named Children
406
+
407
+ PascalCase or numeric keys become child elements:
408
+
409
+ ```js
410
+ export const Card = {
411
+ flow: 'y',
412
+ Header: {
413
+ flow: 'x',
414
+ Title: { text: ({ props }) => props.title },
415
+ },
416
+ Body: { html: ({ props }) => props.content },
417
+ Footer: {
418
+ CloseButton: { extends: 'SquareButton', icon: 'x' }
419
+ }
420
+ }
421
+ ```
422
+
423
+ ### `childExtends`
424
+
425
+ Extend all direct children. Use a named component string:
426
+
427
+ ```js
428
+ export const NavList = { childExtends: 'NavLink' }
429
+ ```
430
+
431
+ ### `childExtendsRecursive`
432
+
433
+ Apply to ALL descendants:
434
+
435
+ ```js
436
+ export const Tree = { childExtendsRecursive: { fontSize: 'A' } }
437
+ ```
438
+
439
+ ### `children` (Dynamic Child List)
440
+
441
+ ```js
442
+ export const DropdownList = {
443
+ children: ({ props }) => props.options || [],
444
+ childExtends: 'OptionItem'
445
+ }
446
+ ```
447
+
448
+ ### `childrenAs`
449
+
450
+ Control how children data maps to elements:
451
+
452
+ | Value | Behavior |
453
+ |---|---|
454
+ | `'props'` (default) | Each item becomes child's `props` |
455
+ | `'state'` | Each item becomes child's `state` |
456
+ | `'element'` | Each item is used directly as element definition |
457
+
458
+ ```js
459
+ { children: [{ text: 'Hello' }] } // -> { props: { text: 'Hello' } }
460
+ { children: [{ count: 5 }], childrenAs: 'state' } // -> { state: { count: 5 } }
461
+ { children: [{ tag: 'span', text: 'Hi' }], childrenAs: 'element' } // -> { tag: 'span', text: 'Hi' }
462
+ ```
463
+
464
+ ### `state: 'key'` (Narrow State Scope)
465
+
466
+ Narrow parent state for children:
467
+
468
+ ```js
469
+ export const TeamList = {
470
+ state: 'members',
471
+ childExtends: 'TeamItem',
472
+ children: ({ state }) => state
473
+ }
474
+
475
+ export const TeamItem = {
476
+ state: true, // REQUIRED for children to receive individual state
477
+ Title: { text: ({ state }) => state.name }
478
+ }
479
+ ```
480
+
481
+ `state: true` is required on child components reading `({ state }) => state.field` when used with `childExtends`.
482
+
483
+ ### `content` (Single Dynamic Child)
484
+
485
+ ```js
486
+ export const Page = { content: ({ props }) => props.page }
487
+ ```
488
+
489
+ ### Children as Async
490
+
491
+ ```js
492
+ {
493
+ children: async (element, state, context) => await window.fetch('...endpoint'),
494
+ childrenAs: 'state',
495
+ }
496
+ ```
497
+
498
+ ---
499
+
500
+ ## Props
501
+
502
+ ### Pass and Access
503
+
504
+ ```js
505
+ // Pass (consumer side)
506
+ { extends: 'Button', props: { text: 'Submit', href: '/dashboard', disabled: false } }
507
+
508
+ // Access (definition side)
509
+ attr: {
510
+ placeholder: ({ props }) => props.placeholder,
511
+ value: (el) => el.props.value,
512
+ disabled: ({ props }) => props.disabled || null
513
+ }
514
+ text: ({ props }) => props.label
515
+ ```
516
+
517
+ ### Boolean/Computed Props
518
+
519
+ `is*`, `has*`, `use*` prefixes are treated as boolean flags:
520
+
521
+ ```js
522
+ isActive: ({ key, state }) => state.active === key
523
+ hasIcon: ({ props }) => Boolean(props.icon)
524
+ useCache: true
525
+ ```
526
+
527
+ ### `childProps`
528
+
529
+ Inject props into all named children:
530
+
531
+ ```js
532
+ export const Layout = {
533
+ childProps: {
534
+ onClick: (ev) => { ev.stopPropagation() }
535
+ }
536
+ }
537
+ ```
538
+
539
+ ---
540
+
541
+ ## `define` (Custom Property Transformers)
542
+
543
+ ```js
544
+ define: {
545
+ isActive: (param, el, state, context) => {
546
+ if (param) el.classList.add('active')
547
+ else el.classList.remove('active')
548
+ }
549
+ }
550
+ ```
551
+
552
+ ### Built-In Defines
553
+
554
+ | Define | Purpose |
555
+ |---|---|
556
+ | `metadata` | SEO metadata (see SEO-METADATA.md) |
557
+ | `routes` | Route definitions for the router |
558
+ | `$router` | Render route content into the element |
559
+
560
+ ```js
561
+ export const aboutPage = {
562
+ metadata: {
563
+ title: 'About Us',
564
+ description: (el, s) => s.aboutText,
565
+ 'og:image': '/about.png'
566
+ }
567
+ }
568
+ ```
569
+
570
+ ---
571
+
572
+ ## `if` (Conditional Rendering)
573
+
574
+ ```js
575
+ export const AuthView = {
576
+ if: (el, state) => state.isAuthenticated,
577
+ Dashboard: { /* renders only when true */ }
578
+ }
579
+
580
+ export const ErrorMsg = {
581
+ if: ({ props }) => Boolean(props.error),
582
+ text: ({ props }) => props.error
583
+ }
584
+ ```
585
+
586
+ ---
587
+
588
+ ## `scope` and `data`
589
+
590
+ ```js
591
+ // scope: 'state' -- element.scope becomes element.state
592
+ export const Form = {
593
+ scope: 'state',
594
+ state: { name: '', email: '' }
595
+ }
596
+
597
+ // data -- non-reactive storage (no re-renders)
598
+ export const Chart = {
599
+ data: { chartInstance: null },
600
+ onRender: (el) => {
601
+ el.data.chartInstance = new Chart(el.node, { /* ... */ })
602
+ }
603
+ }
604
+ ```
605
+
606
+ ---
607
+
608
+ ## Element Methods
609
+
610
+ | Category | Method | Description |
611
+ |---|---|---|
612
+ | **Navigation** | `el.lookup('key')` | Find ancestor by key or predicate |
613
+ | | `el.lookdown('key')` | Find first descendant by key |
614
+ | | `el.lookdownAll('key')` | Find all descendants by key |
615
+ | | `el.spotByPath(['Header', 'Nav', 'Logo'])` | Find by array path |
616
+ | | `el.nextElement()` | Next sibling |
617
+ | | `el.previousElement()` | Previous sibling |
618
+ | | `el.getRoot()` | Root element |
619
+ | **Updates** | `el.update({ key: value })` | Deep overwrite element properties |
620
+ | | `el.set({ key: value })` | Set content element |
621
+ | | `el.setProps({ key: value })` | Update props specifically |
622
+ | **Content** | `el.updateContent(newContent)` | Update content |
623
+ | | `el.removeContent()` | Remove content |
624
+ | **State** | `el.getRootState()` | App-level root state |
625
+ | | `el.getRootState('key')` | Specific key from root state |
626
+ | | `el.getContext('key')` | Value from element's context |
627
+ | **DOM** | `el.setNodeStyles({ key: value })` | Apply inline styles |
628
+ | | `el.remove()` | Remove from tree and DOM |
629
+ | **Context** | `el.call('fnKey', ...args)` | Lookup: `context.utils -> functions -> methods -> snippets` |
630
+ | **Router** | `el.router(path, root)` | SPA navigation |
631
+ | **Dependencies** | `el.require('moduleName')` | Cross-environment dependency loading |
632
+ | **Debug** | `el.parse(exclude)` | One-level purified plain object |
633
+ | | `el.parseDeep(exclude)` | Deep purified plain object |
634
+ | | `el.keys()` | List element's own keys |
635
+ | | `el.verbose()` | Log element in console |
636
+
637
+ ### Element Update Options
638
+
639
+ Pass as second argument to `el.update(value, options)`:
640
+
641
+ | Option | Description |
642
+ |---|---|
643
+ | `onlyUpdate` | Only update specific subtree by key |
644
+ | `preventUpdate` | Prevent element update |
645
+ | `preventStateUpdate` | Prevent state update |
646
+ | `preventUpdateListener` | Skip update event listeners |
647
+ | `preventUpdateAfter` | Skip post-update hooks |
648
+ | `lazyLoad` | Enable lazy loading for the update |
649
+
650
+ ---
651
+
652
+ ## `el.call()` (Context Function Lookup)
653
+
654
+ Lookup order: `context.utils -> context.functions -> context.methods -> context.snippets`
655
+
656
+ ```js
657
+ el.call('router', href, root, {}, options)
658
+ el.call('exec', value, el)
659
+ el.call('isString', value)
660
+ el.call('fetchData', id)
661
+ el.call('replaceLiteralsWithObjectFields', template)
662
+ ```
663
+
664
+ ---
665
+
666
+ ## Router
667
+
668
+ ### Declare Pages
669
+
670
+ ```js
671
+ // pages/index.js
672
+ export default {
673
+ '/': homePage,
674
+ '/dashboard': dashboardPage,
675
+ }
676
+ ```
677
+
678
+ ### Link Navigation
679
+
680
+ ```js
681
+ export const NavItem = {
682
+ extends: 'Link',
683
+ text: ({ props }) => props.label,
684
+ href: '/dashboard'
685
+ }
686
+ ```
687
+
688
+ ### Programmatic Navigation
689
+
690
+ Call `event.preventDefault()` BEFORE the router call:
691
+
692
+ ```js
693
+ onClick: (event, el) => {
694
+ event.preventDefault()
695
+ el.call('router', '/dashboard', el.__ref.root, {}, {
696
+ scrollToTop: true,
697
+ scrollToOptions: { behavior: 'instant' }
698
+ })
699
+ }
700
+ ```
701
+
702
+ ### Custom Router Element (Persistent Layouts)
703
+
704
+ Configure in `config.js` to render pages inside a specific element:
705
+
706
+ ```js
707
+ // config.js
708
+ export default {
709
+ router: {
710
+ customRouterElement: 'Folder.Content' // dot-separated path from root
711
+ }
712
+ }
713
+ ```
714
+
715
+ The `/` page defines the persistent layout shell. Sub-pages render inside the target element without destroying the layout.
716
+
717
+ ---
718
+
719
+ ## `element.require()` (Cross-Environment Dependency Loading)
720
+
721
+ ```js
722
+ {
723
+ tag: 'canvas',
724
+ onRender: async (element, state) => {
725
+ const Chart = element.require('chartjs')
726
+ const ctx = element.node.getContext('2d')
727
+ new Chart(ctx, { type: 'bar', data: { /* ... */ } })
728
+ }
729
+ }
730
+ ```
731
+
732
+ ---
733
+
734
+ ## Common Patterns
735
+
736
+ ### Loading State
737
+
738
+ ```js
739
+ export const DataList = {
740
+ state: { items: [], loading: true, error: null },
741
+ Loader: { if: ({ state }) => state.loading, extends: 'Spinner' },
742
+ Error: { if: ({ state }) => Boolean(state.error), text: ({ state }) => state.error },
743
+ Items: {
744
+ if: ({ state }) => !state.loading && !state.error,
745
+ children: ({ state }) => state.items,
746
+ childExtends: 'ListItem'
747
+ },
748
+ onRender: async (el, state) => {
749
+ try {
750
+ const items = await el.call('fetchItems')
751
+ state.update({ items, loading: false })
752
+ } catch (e) {
753
+ state.update({ error: e.message, loading: false })
754
+ }
755
+ }
756
+ }
757
+ ```
758
+
759
+ ### Active List Item
760
+
761
+ ```js
762
+ export const Menu = {
763
+ state: { active: null },
764
+ childExtends: 'MenuItem',
765
+ childProps: {
766
+ isActive: ({ key, state }) => state.active === key,
767
+ '.active': { fontWeight: '600', color: 'primary' },
768
+ onClick: (ev, el, state) => { state.update({ active: el.key }) }
769
+ }
770
+ }
771
+ ```
772
+
773
+ ### Modal
774
+
775
+ ```js
776
+ export const ModalCard = {
777
+ position: 'absolute', align: 'center center',
778
+ top: 0, left: 0, boxSize: '100% 100%',
779
+ transition: 'all C defaultBezier',
780
+ opacity: '0', visibility: 'hidden', pointerEvents: 'none', zIndex: '-1',
781
+
782
+ isActive: (el, s) => s.root.activeModal,
783
+ '.isActive': { opacity: '1', zIndex: 999999, visibility: 'visible', pointerEvents: 'initial' },
784
+
785
+ onClick: (event, element) => { element.call('closeModal') },
786
+ childProps: { onClick: (ev) => { ev.stopPropagation() } },
787
+ }
788
+ ```
789
+
790
+ ---
791
+
792
+ ## Naming Conventions
793
+
794
+ | Category | Convention | Examples |
795
+ |---|---|---|
796
+ | Components | PascalCase | `CustomComponent`, `NavBar`, `UserProfile` |
797
+ | Properties | camelCase | `paddingInlineStart`, `fontSize`, `backgroundColor` |
798
+ | Repeating keys | Snake_Case suffixes | `Li_1`, `Li_2`, `Li_One` |
799
+
800
+ ---
801
+
802
+ ## Reserved Keywords
803
+
804
+ These keys are handled by the DOMQL engine and are NOT CSS props or child components:
805
+
806
+ ```
807
+ key, extends, childExtends, childExtendsRecursive, childProps, props,
808
+ state, tag, query, data, scope, children, childrenAs, context
809
+ ```
810
+
811
+ All other keys: lowercase/camelCase = CSS props, PascalCase = child components.
812
+
813
+ ---
814
+
815
+ ## Finding DOMQL Elements in Browser DOM
816
+
817
+ Every DOMQL-managed DOM node has `.ref` pointing to its DOMQL element:
818
+
819
+ ```js
820
+ const domqlElement = someNode.ref
821
+ domqlElement.key // element key name
822
+ domqlElement.props // current props
823
+ domqlElement.state // element state
824
+ domqlElement.parent // parent DOMQL element
825
+
826
+ // Find by key
827
+ for (const node of document.querySelectorAll('*')) {
828
+ if (node.ref?.key === 'ModalCard') { /* ... */ break }
829
+ }
830
+
831
+ // Debug CSS state
832
+ ref.__ref.__class // CSS object input to Emotion
833
+ ref.__ref.__classNames // generated Emotion class names
834
+ window.getComputedStyle(ref.node).opacity
835
+ ```