@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,561 @@
1
+ # Migration Rules: DOMQL v2 to v3 & Framework to Symbols
2
+
3
+ Apply these transformation rules when migrating code. Each rule is a BEFORE/AFTER pair.
4
+
5
+ ---
6
+
7
+ ## Part 1: DOMQL v2 to v3
8
+
9
+ ### Rule Summary
10
+
11
+ | What changed | v2 (REMOVE) | v3 (USE INSTEAD) |
12
+ | ----------------------- | ----------------------------- | ----------------------------- |
13
+ | CSS props wrapper | `props: { padding: 'A' }` | `padding: 'A'` (at root) |
14
+ | Events wrapper | `on: { click: fn }` | `onClick: fn` (at root) |
15
+ | Inheritance | `extend: 'Component'` | `extends: 'Component'` |
16
+ | Child extend | `childExtend: 'Item'` | `childExtends: 'Item'` |
17
+ | Child element detection | any key including lowercase | PascalCase keys ONLY |
18
+ | Base components | `extends: 'Text'` / `'Box'` | Remove — implicit defaults |
19
+
20
+ ### Rule 1: Flatten `props` and `on`
21
+
22
+ Move all `props` entries to root. Remove `on` wrapper; prefix each event with `on` + CapitalizedEventName. Apply at root AND all nested elements.
23
+
24
+ BEFORE:
25
+ ```js
26
+ {
27
+ props: { position: 'absolute', gap: 'A' },
28
+ on: {
29
+ render: e => {},
30
+ click: (e, t) => {},
31
+ wheel: (e, t) => {},
32
+ },
33
+ }
34
+ ```
35
+
36
+ AFTER:
37
+ ```js
38
+ {
39
+ position: 'absolute',
40
+ gap: 'A',
41
+ onRender: e => {},
42
+ onClick: (e, t) => {},
43
+ onWheel: (e, t) => {},
44
+ }
45
+ ```
46
+
47
+ ### Rule 2: Rename `extend` to `extends`, `childExtend` to `childExtends`
48
+
49
+ BEFORE:
50
+ ```js
51
+ { extend: SomeComponent, childExtend: AnotherComponent }
52
+ ```
53
+
54
+ AFTER:
55
+ ```js
56
+ { extends: SomeComponent, childExtends: AnotherComponent }
57
+ ```
58
+
59
+ ### Rule 3: Replace `$stateCollection` with `children` + `childrenAs: 'state'`
60
+
61
+ BEFORE:
62
+ ```js
63
+ {
64
+ childExtend: 'TeamItem',
65
+ $stateCollection: (el, s) => s.data
66
+ }
67
+ ```
68
+
69
+ AFTER (Option A -- explicit):
70
+ ```js
71
+ {
72
+ childExtends: 'TeamItem',
73
+ childrenAs: 'state',
74
+ children: (el, s) => s.data
75
+ }
76
+ ```
77
+
78
+ AFTER (Option B -- preferred for nested state):
79
+ ```js
80
+ {
81
+ state: 'data',
82
+ childExtends: 'TeamItem',
83
+ children: ({ state }) => state
84
+ }
85
+ ```
86
+
87
+ **`state: true` requirement for child components:** Components used with `childExtends` that read individual item state need `state: true` at their root so each child receives its own state from the parent's children array.
88
+
89
+ BEFORE (parent + child):
90
+ ```js
91
+ // Parent container
92
+ export const TeamList = {
93
+ state: 'members',
94
+ childExtends: 'TeamItem',
95
+ children: ({ state }) => state
96
+ }
97
+
98
+ // Child component -- state: true is REQUIRED
99
+ export const TeamItem = {
100
+ state: true,
101
+ Title: { text: ({ state }) => state.name },
102
+ Role: { text: ({ state }) => state.role }
103
+ }
104
+ ```
105
+
106
+ **CRITICAL PITFALL -- `children: ({ state }) => state.data`:**
107
+
108
+ WRONG -- children don't get individual state:
109
+ ```js
110
+ { children: ({ state }) => state.data, childExtends: 'Item' }
111
+ ```
112
+
113
+ CORRECT -- `state: 'data'` narrows scope, children get individual items:
114
+ ```js
115
+ { state: 'data', children: ({ state }) => state, childExtends: 'Item' }
116
+ ```
117
+
118
+ ### Rule 4: Replace `$propsCollection` with `children`
119
+
120
+ `$propsCollection` is fully removed in v3. Replace 1:1 with `children`.
121
+
122
+ BEFORE:
123
+ ```js
124
+ {
125
+ childExtend: 'Paragraph',
126
+ $propsCollection: ({ state }) => state.parse()
127
+ }
128
+ ```
129
+
130
+ AFTER:
131
+ ```js
132
+ {
133
+ childExtends: 'Paragraph',
134
+ children: ({ state }) => state.parse()
135
+ }
136
+ ```
137
+
138
+ Also applies to array data:
139
+
140
+ BEFORE: `$propsCollection: ({ state }) => state`
141
+ AFTER: `children: ({ state }) => state`
142
+
143
+ ### Rule 5: Replace `props: (fn)` function with individual prop functions
144
+
145
+ Dynamic props functions are REMOVED in v3. Split into per-property functions.
146
+
147
+ BEFORE:
148
+ ```js
149
+ {
150
+ props: ({ state }) => ({
151
+ color: state.active ? 'red' : 'blue',
152
+ opacity: state.loading ? 0.5 : 1
153
+ })
154
+ }
155
+ ```
156
+
157
+ AFTER:
158
+ ```js
159
+ {
160
+ color: ({ state }) => state.active ? 'red' : 'blue',
161
+ opacity: ({ state }) => state.loading ? 0.5 : 1
162
+ }
163
+ ```
164
+
165
+ ### Rule 6: PascalCase-only child elements
166
+
167
+ In v3, only PascalCase keys create child elements. Lowercase keys are treated as CSS properties.
168
+
169
+ BEFORE (v2):
170
+ ```js
171
+ { div: {} } // creates <div>
172
+ ```
173
+
174
+ AFTER (v3):
175
+ ```js
176
+ { div: {} } // treated as a plain prop, NOT rendered
177
+ { Div: {} } // creates a child element (equivalent to { extends: 'Div' })
178
+ ```
179
+
180
+ ### Rule 7: Replace array spread with `children` array
181
+
182
+ Array spread creates numeric keys (`0`, `1`, `2`...) which v3 treats as CSS properties. Always use `children` array.
183
+
184
+ BEFORE:
185
+ ```js
186
+ {
187
+ childExtend: 'NavLink',
188
+ ...[{ text: 'About us' }, { text: 'Hiring' }]
189
+ }
190
+ ```
191
+
192
+ AFTER:
193
+ ```js
194
+ {
195
+ childExtends: 'NavLink',
196
+ children: [{ text: 'About us' }, { text: 'Hiring' }]
197
+ }
198
+ ```
199
+
200
+ ### Rule 8: Picture `src` must go on Img child
201
+
202
+ 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`.
203
+
204
+ WRONG:
205
+ ```js
206
+ Picture: { src: '/files/photo.jpg', width: '100%' }
207
+ ```
208
+
209
+ CORRECT:
210
+ ```js
211
+ Picture: {
212
+ Img: { src: '/files/photo.jpg' },
213
+ width: '100%',
214
+ aspectRatio: '16/9'
215
+ }
216
+ ```
217
+
218
+ For theme-aware sources, use `@dark`/`@light` with `srcset` on Picture, but always put the default `src` on the Img child.
219
+
220
+ ### Rule 9: `<map>` tag auto-detection
221
+
222
+ Component keys named `Map` auto-detect as the HTML `<map>` tag (for image maps), which defaults to `display: inline` and has height 0. If using `Map` as a component name for geographic maps or similar, add `tag: 'div'`:
223
+
224
+ ```js
225
+ export const Map = {
226
+ flow: 'y',
227
+ tag: 'div', // prevents <map> auto-detection
228
+ // ...
229
+ }
230
+ ```
231
+
232
+ ### Rule 10: Non-existent base components
233
+
234
+ These v2 component names don't exist in v3 uikit:
235
+
236
+ | v2 (REMOVE) | v3 (USE INSTEAD) |
237
+ | ---------------------- | ----------------------------------------- |
238
+ | `extends: 'Page'` | `flow: 'column'` |
239
+ | `extends: 'Overflow'` | `flow: 'x'` |
240
+
241
+ ### Rule 11: `state: true` vs `state: 'fieldName'` for children
242
+
243
+ | Context | Use | Purpose |
244
+ | ---------------------------------- | ---------------- | --------------------------------------------------------- |
245
+ | `childExtends`-created children | `state: true` | Maps individual array items as each child's state |
246
+ | Direct PascalCase children | `state: 'field'` | Picks a specific field from parent state |
247
+
248
+ ```js
249
+ // childExtends children -- use state: true on the child component
250
+ export const ListItem = {
251
+ state: true,
252
+ Title: { text: ({ state }) => state.name }
253
+ }
254
+
255
+ // Direct PascalCase child -- use state: 'fieldName'
256
+ Description: {
257
+ state: 'description',
258
+ childExtends: 'Paragraph',
259
+ children: ({ state }) => state.parse()
260
+ }
261
+ ```
262
+
263
+ ### Rule 12: Remove `extends: 'Text'` and `extends: 'Box'`
264
+
265
+ `Text` and `Box` are built-in implicit defaults. Extending them causes unnecessary merge at runtime.
266
+
267
+ BEFORE:
268
+ ```js
269
+ Tag: { extends: 'Text', text: 'NEW', padding: 'X A' }
270
+ Card: { extends: 'Box', padding: 'B', background: 'white' }
271
+ ```
272
+
273
+ AFTER:
274
+ ```js
275
+ Tag: { tag: 'span', text: 'NEW', padding: 'X A' }
276
+ Card: { tag: 'div', padding: 'B', background: 'white' }
277
+ ```
278
+
279
+ Use semantic or functional components instead (`Flex`, `Link`, `Button`, `Header`, `Section`, etc.).
280
+
281
+ ### Full v2 to v3 transformation example
282
+
283
+ BEFORE:
284
+ ```js
285
+ {
286
+ extend: 'Flex',
287
+ childExtend: 'ListItem',
288
+ props: { position: 'absolute' },
289
+ attr: { 'gs-w': 1, 'gs-h': 1 },
290
+ SectionTitle: { text: 'Notes' },
291
+ Box: {
292
+ props: {
293
+ '--section-background': '#7a5e0e55',
294
+ id: 'editorjs',
295
+ },
296
+ on: {
297
+ frame: (e, t) => {},
298
+ render: e => {},
299
+ wheel: (e, t) => {},
300
+ dblclick: (e, t) => { e.stopPropagation() },
301
+ },
302
+ },
303
+ }
304
+ ```
305
+
306
+ AFTER:
307
+ ```js
308
+ {
309
+ flow: 'x',
310
+ childExtends: 'ListItem',
311
+ position: 'absolute',
312
+ attr: { 'gs-w': 1, 'gs-h': 1 },
313
+ SectionTitle: { text: 'Notes' },
314
+ Box: {
315
+ '--section-background': '#7a5e0e55',
316
+ id: 'editorjs',
317
+ onFrame: (e, t) => {},
318
+ onRender: e => {},
319
+ onWheel: (e, t) => {},
320
+ onDblclick: (e, t) => { e.stopPropagation() },
321
+ },
322
+ }
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Part 2: React / Angular / Vue to Symbols
328
+
329
+ ### React to Symbols
330
+
331
+ | React Pattern | Symbols Equivalent |
332
+ | -------------------------------------- | ------------------------------------------------------------------------- |
333
+ | `function Component()` / `class` | `export const Component = { flow: 'y', ... }` |
334
+ | `import Component from './Component'` | Reference by key: `{ Component: {} }` |
335
+ | `useState(val)` | `state: { key: val }` + `s.update({ key: newVal })` |
336
+ | `useEffect(() => {}, [])` | `onRender: (el, s) => {}` |
337
+ | `useEffect(() => {}, [dep])` | `onStateUpdate: (changes, el, s) => {}` |
338
+ | `useContext` | `s.root` for global state |
339
+ | `useRef` | `el.node` for DOM access |
340
+ | `props.onClick` | `onClick: (e, el, s) => {}` |
341
+ | `props.children` | PascalCase child keys or `children` array |
342
+ | `{condition && <Component />}` | `if: (el, s) => condition` |
343
+ | `{items.map(i => <Item key={i.id}/>)}` | `children: (el, s) => s.items, childExtends: 'Item'` |
344
+ | `className="flex gap-4"` | `flow: 'x', gap: 'A'` (design tokens) |
345
+ | `style={{ padding: '16px' }}` | `padding: 'A'` |
346
+ | `<Link to="/page">` | `Link: { href: '/page', text: '...' }` |
347
+ | `useNavigate()` / `history.push` | `el.call('router', '/path', el.getRoot())` |
348
+ | Redux / Zustand store | `state/` folder + `s.root` access |
349
+ | `useMemo` / `useCallback` | Dynamic prop function: `text: (el, s) => s.a + s.b` |
350
+ | CSS Modules / styled-components | Flatten styles as props with design tokens |
351
+ | `<form onSubmit>` | `tag: 'form', onSubmit: (ev, el, s) => { ev.preventDefault(); ... }` |
352
+ | `fetch()` in components | `functions/fetch.js` + `el.call('fetch', method, path, data)` |
353
+
354
+ ### Angular to Symbols
355
+
356
+ | Angular Pattern | Symbols Equivalent |
357
+ | ---------------------------------- | ---------------------------------------------------------------------- |
358
+ | `@Component({ template, styles })` | Plain object with flattened props and child keys |
359
+ | `@Input() propName` | `propName: value` at root |
360
+ | `@Output() eventName` | `onEventName: (e, el, s) => {}` |
361
+ | `*ngIf="condition"` | `if: (el, s) => condition` |
362
+ | `*ngFor="let item of items"` | `children: (el, s) => s.items, childExtends: 'X'` |
363
+ | `[ngClass]="{ active: isActive }"` | `.isActive: { background: 'primary' }` |
364
+ | `(click)="handler()"` | `onClick: (e, el, s) => {}` |
365
+ | `{{ interpolation }}` | `text: '{{ key }}'` or `text: (el, s) => s.key` |
366
+ | `ngOnInit()` | `onInit: (el, s) => {}` |
367
+ | `ngAfterViewInit()` | `onRender: (el, s) => {}` |
368
+ | `ngOnDestroy()` | Return cleanup fn from `onRender` |
369
+ | `ngOnChanges(changes)` | `onStateUpdate: (changes, el, s) => {}` |
370
+ | Services / DI | `functions/` folder + `el.call('serviceFn', args)` |
371
+ | `RouterModule` routes | `pages/index.js` route mapping |
372
+ | `routerLink="/path"` | `Link: { href: '/path' }` |
373
+ | `Router.navigate(['/path'])` | `el.call('router', '/path', el.getRoot())` |
374
+ | NgRx / BehaviorSubject store | `state/` folder + `s.root` access |
375
+ | SCSS / component styles | Flatten to props with design tokens |
376
+ | Reactive Forms | `tag: 'form'`, `Input` children with `name`, `onSubmit` handler |
377
+
378
+ ### Vue to Symbols
379
+
380
+ | Vue Pattern | Symbols Equivalent |
381
+ | --------------------------------- | --------------------------------------------------------------------------------- |
382
+ | `<template>` + `<script>` SFC | Single object with child keys and flattened props |
383
+ | `:propName="value"` (v-bind) | `propName: value` or `propName: (el, s) => s.value` |
384
+ | `@click="handler"` (v-on) | `onClick: (e, el, s) => {}` |
385
+ | `v-if="condition"` | `if: (el, s) => condition` |
386
+ | `v-show="condition"` | `hide: (el, s) => !condition` |
387
+ | `v-for="item in items"` | `children: (el, s) => s.items, childExtends: 'X'` |
388
+ | `v-model="value"` | `value: '{{ key }}'` + `onInput: (e, el, s) => s.update({ key: e.target.value })` |
389
+ | `ref="myRef"` | `el.node` or `el.lookup('Key')` |
390
+ | `data()` / `ref()` / `reactive()` | `state: { key: value }` |
391
+ | `computed` | `text: (el, s) => s.first + ' ' + s.last` |
392
+ | `watch` | `onStateUpdate: (changes, el, s) => {}` |
393
+ | `mounted()` | `onRender: (el, s) => {}` |
394
+ | `created()` / `setup()` | `onInit: (el, s) => {}` |
395
+ | Vuex / Pinia store | `state/` folder + `s.root` access |
396
+ | `<router-link to="/path">` | `Link: { href: '/path' }` |
397
+ | `$router.push('/path')` | `el.call('router', '/path', el.getRoot())` |
398
+ | `<slot>` | PascalCase child keys or `content` property |
399
+ | `<slot name="header">` | Named child key: `Header: {}` |
400
+ | Mixins / Composables | `extends` for shared logic; `functions/` for shared utilities |
401
+ | `$emit('eventName', data)` | `s.parent.update({ key: data })` or callback via state |
402
+
403
+ ---
404
+
405
+ ## Part 3: CSS to Design Tokens
406
+
407
+ | CSS | Symbols |
408
+ | ----------------------------------------------------- | -------------------------------------------- |
409
+ | `padding: 16px` | `padding: 'A'` |
410
+ | `padding: 16px 26px` | `padding: 'A B'` |
411
+ | `margin: 0 auto` | `margin: '- auto'` |
412
+ | `gap: 10px` | `gap: 'Z'` |
413
+ | `border-radius: 12px` | `borderRadius: 'Z1'` or `round: 'Z1'` |
414
+ | `width: 42px; height: 42px` | `boxSize: 'C'` |
415
+ | `display: flex; flex-direction: column` | `flow: 'y'` |
416
+ | `display: flex; flex-direction: row` | `flow: 'x'` |
417
+ | `align-items: center; justify-content: center` | `align: 'center center'` |
418
+ | `display: grid; grid-template-columns: repeat(3,1fr)` | `extends: 'Grid', columns: 'repeat(3, 1fr)'` |
419
+ | `font-size: 20px` | `fontSize: 'A1'` |
420
+ | `font-weight: 500` | `fontWeight: '500'` |
421
+ | `color: rgba(255,255,255,0.65)` | `color: 'white.65'` |
422
+ | `background: #000` | `background: 'black'` |
423
+ | `background: rgba(0,0,0,0.5)` | `background: 'black.5'` |
424
+ | `cursor: pointer` | `cursor: 'pointer'` |
425
+ | `overflow: hidden` | `overflow: 'hidden'` |
426
+ | `position: absolute; inset: 0` | `position: 'absolute', inset: '0'` |
427
+ | `z-index: 99` | `zIndex: 99` |
428
+ | `transition: all 0.3s ease` | `transition: 'A defaultBezier'` |
429
+ | `:hover { background: #333 }` | `':hover': { background: 'gray3' }` |
430
+ | `@media (max-width: 768px) { ... }` | `'@mobileL': { ... }` |
431
+
432
+ ### Spacing token quick reference
433
+
434
+ | Token | ~px | Token | ~px | Token | ~px |
435
+ | ----- | --- | ----- | --- | ----- | --- |
436
+ | X | 3 | A | 16 | D | 67 |
437
+ | Y | 6 | A1 | 20 | E | 109 |
438
+ | Z | 10 | A2 | 22 | F | 177 |
439
+ | Z1 | 12 | B | 26 | | |
440
+ | Z2 | 14 | B1 | 32 | | |
441
+ | | | B2 | 36 | | |
442
+ | | | C | 42 | | |
443
+ | | | C1 | 52 | | |
444
+ | | | C2 | 55 | | |
445
+
446
+ ---
447
+
448
+ ## Part 4: v3 Component Template
449
+
450
+ Use this as the base template for every new component:
451
+
452
+ ```js
453
+ // components/ComponentName.js
454
+ export const ComponentName = {
455
+ // Props flattened at root (design tokens)
456
+ flow: 'y',
457
+ padding: 'A B',
458
+ background: 'surface',
459
+ borderRadius: 'B',
460
+ gap: 'Z',
461
+
462
+ // Lifecycle events
463
+ onRender: (el, s) => {},
464
+ onInit: (el, s) => {},
465
+
466
+ // DOM events
467
+ onClick: (e, el, s) => {},
468
+
469
+ // Conditional cases (v3 pattern)
470
+ isActive: false,
471
+ '.isActive': { background: 'primary', color: 'white' },
472
+
473
+ // Responsive
474
+ '@mobileL': { padding: 'A' },
475
+ '@tabletS': { padding: 'B' },
476
+
477
+ // Children -- PascalCase keys, no imports
478
+ Header: {},
479
+ Content: {
480
+ Article: { text: 'Hello' },
481
+ },
482
+ Footer: {},
483
+ }
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Part 5: State Management Migration
489
+
490
+ | Framework pattern | Symbols equivalent |
491
+ | --------------------------------------- | ----------------------------------------------------------------------------- |
492
+ | Global store (Redux, Pinia, NgRx) | `state/index.js` with initial flat state. Access via `s.root` in any component |
493
+ | Local component state | `state: { key: val }` on the component |
494
+ | Derived/computed state | Dynamic prop function: `text: (el, s) => derived(s)` |
495
+ | Async data fetch | `onRender: async (el, s) => { ... s.update({data}) }` |
496
+ | State persistence | Functions that read/write to localStorage/cookie |
497
+
498
+ Async state pattern:
499
+
500
+ ```js
501
+ export const DataView = {
502
+ state: { data: null, loading: true, error: null },
503
+
504
+ onRender: async (el, state) => {
505
+ try {
506
+ const data = await el.call('fetchData', el.props.id)
507
+ state.update({ data, loading: false })
508
+ } catch (e) {
509
+ state.update({ error: e.message, loading: false })
510
+ }
511
+ }
512
+ }
513
+ ```
514
+
515
+ ---
516
+
517
+ ## Part 6: Color/Border/Shadow Syntax Migration (v3.1)
518
+
519
+ ### Color tokens: space-separated to dot-notation
520
+
521
+ | BEFORE | AFTER | Notes |
522
+ | ----------------- | ---------------- | ----------------------------- |
523
+ | `'white .1'` | `'white.1'` | Opacity 0.1 |
524
+ | `'gray 0.85'` | `'gray.85'` | Opacity 0.85 |
525
+ | `'gray .92 +8'` | `'gray.92+8'` | Opacity + relative tone |
526
+ | `'gray 1 +16'` | `'gray+16'` | Alpha 1 = default, omit |
527
+ | `'gray 1 -68'` | `'gray-68'` | Relative tone |
528
+ | `'gray 1 90'` | `'gray=90'` | Absolute lightness (=prefix) |
529
+ | `'white 1 -78'` | `'white-78'` | Tone only |
530
+
531
+ ### border: comma-separated to space-separated (CSS order)
532
+
533
+ | BEFORE | AFTER |
534
+ | --------------------------- | ----------------------- |
535
+ | `'solid, gray, 1px'` | `'1px solid gray'` |
536
+ | `'gray6 .1, solid, 1px'` | `'1px solid gray6.1'` |
537
+ | `'solid, mediumGrey'` | `'solid mediumGrey'` |
538
+ | `'1px, solid'` | `'1px solid'` |
539
+
540
+ ### boxShadow: commas to spaces within shadow, pipe to comma between shadows
541
+
542
+ | BEFORE | AFTER |
543
+ | ----------------------------------- | --------------------------- |
544
+ | `'white .1, 0, A, C, C'` | `'white.1 0 A C C'` |
545
+ | `'black .10, 0px, 2px, 8px, 0px'` | `'black.1 0px 2px 8px 0px'` |
546
+ | `'a, b \| c, d'` | `'a b, c d'` |
547
+
548
+ ### textStroke/textShadow: comma-separated to space-separated
549
+
550
+ | BEFORE | AFTER |
551
+ | --------------------- | ------------------ |
552
+ | `'1px, gray6'` | `'1px gray6'` |
553
+ | `'gray1, 6px, 6px'` | `'gray1 6px 6px'` |
554
+
555
+ ### CSS fallback
556
+
557
+ Raw CSS values pass through unchanged:
558
+ ```js
559
+ boxShadow: '0 2px 8px rgba(0,0,0,0.1)' // passes through as-is
560
+ border: '1px solid #333' // passes through as-is
561
+ ```