@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,509 @@
|
|
|
1
|
+
# Symbols / DOMQL — UI Patterns, Accessibility & AI Optimization
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Part 1: UI/UX Patterns
|
|
6
|
+
|
|
7
|
+
### Loading State
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
export const DataList = {
|
|
11
|
+
state: { items: [], loading: true, error: null },
|
|
12
|
+
|
|
13
|
+
Loader: { if: ({ state }) => state.loading, extends: 'Spinner' },
|
|
14
|
+
Error: {
|
|
15
|
+
if: ({ state }) => Boolean(state.error),
|
|
16
|
+
attr: { role: 'alert' },
|
|
17
|
+
text: ({ state }) => state.error
|
|
18
|
+
},
|
|
19
|
+
Items: {
|
|
20
|
+
if: ({ state }) => !state.loading && !state.error,
|
|
21
|
+
children: ({ state }) => state.items,
|
|
22
|
+
childExtends: 'ListItem'
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
onRender: async (el, state) => {
|
|
26
|
+
try {
|
|
27
|
+
const items = await el.call('fetchItems')
|
|
28
|
+
state.update({ items, loading: false })
|
|
29
|
+
} catch (e) {
|
|
30
|
+
state.update({ error: e.message, loading: false })
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Toggle / Accordion
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
export const Accordion = {
|
|
40
|
+
state: { open: false },
|
|
41
|
+
Header: {
|
|
42
|
+
extends: 'Flex',
|
|
43
|
+
attr: (el, s) => ({
|
|
44
|
+
'aria-expanded': s.open,
|
|
45
|
+
'aria-controls': 'accordion-body'
|
|
46
|
+
}),
|
|
47
|
+
onClick: (ev, el) => { el.parent.state.toggle('open') }
|
|
48
|
+
},
|
|
49
|
+
Body: {
|
|
50
|
+
id: 'accordion-body',
|
|
51
|
+
if: ({ state }) => state.open,
|
|
52
|
+
html: ({ props }) => props.content
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Active List Item
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
export const Menu = {
|
|
61
|
+
state: { active: null },
|
|
62
|
+
childExtends: 'NavLink',
|
|
63
|
+
childProps: {
|
|
64
|
+
isActive: ({ key, state }) => state.active === key,
|
|
65
|
+
'.active': { fontWeight: '600', color: 'primary' },
|
|
66
|
+
onClick: (ev, el, state) => { state.update({ active: el.key }) }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Modal (v3 complete pattern)
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
// components/ModalCard.js
|
|
75
|
+
export const ModalCard = {
|
|
76
|
+
position: 'absolute', flexAlign: 'center center',
|
|
77
|
+
top: 0, left: 0, boxSize: '100% 100%',
|
|
78
|
+
transition: 'all C defaultBezier',
|
|
79
|
+
opacity: '0', visibility: 'hidden', pointerEvents: 'none', zIndex: '-1',
|
|
80
|
+
|
|
81
|
+
isActive: (el, s) => s.root.activeModal,
|
|
82
|
+
'.isActive': { opacity: '1', zIndex: 999999, visibility: 'visible', pointerEvents: 'initial' },
|
|
83
|
+
|
|
84
|
+
// Close on backdrop click, prevent close on content click
|
|
85
|
+
onClick: (event, element) => { element.call('closeModal') },
|
|
86
|
+
childProps: { onClick: (ev) => { ev.stopPropagation() } },
|
|
87
|
+
|
|
88
|
+
// ARIA
|
|
89
|
+
attr: { role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Dialog' },
|
|
90
|
+
|
|
91
|
+
// Trap focus
|
|
92
|
+
onRender: (el) => {
|
|
93
|
+
const focusable = el.node.querySelectorAll('button, [href], input, [tabindex]:not([tabindex="-1"])')
|
|
94
|
+
if (focusable.length) focusable[0].focus()
|
|
95
|
+
},
|
|
96
|
+
onKeydown: (e, el) => {
|
|
97
|
+
if (e.key === 'Escape') el.call('closeModal')
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
InnerContent: { /* ... */ }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// functions/showModal.js
|
|
104
|
+
export const showModal = function showModal(path) {
|
|
105
|
+
const modalEl = this.lookup('ModalCard')
|
|
106
|
+
const modalNode = modalEl.node
|
|
107
|
+
// FadeIn: force browser to paint opacity:0 before transition to opacity:1
|
|
108
|
+
modalNode.style.opacity = '0'
|
|
109
|
+
modalNode.style.visibility = 'visible'
|
|
110
|
+
this.state.root.update({ activeModal: true }, { onlyUpdate: 'ModalCard' })
|
|
111
|
+
modalNode.style.opacity = '0'
|
|
112
|
+
modalNode.offsetHeight // force reflow
|
|
113
|
+
modalNode.style.opacity = ''
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// functions/closeModal.js
|
|
117
|
+
export const closeModal = function closeModal() {
|
|
118
|
+
const modalEl = this.lookup('ModalCard')
|
|
119
|
+
const modalNode = modalEl.node
|
|
120
|
+
modalNode.style.opacity = '0'
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
modalNode.style.opacity = ''
|
|
123
|
+
modalNode.style.visibility = ''
|
|
124
|
+
this.state.root.update({ activeModal: false }, { onlyUpdate: 'ModalCard' })
|
|
125
|
+
}, 280) // match CSS transition duration
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Tab Switching (DOM ID pattern)
|
|
130
|
+
|
|
131
|
+
Use DOM IDs for view switching — NOT reactive `display` bindings (causes rendering failures).
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
// Page definition
|
|
135
|
+
HomeView: { id: 'view-home', extends: 'Flex', ... },
|
|
136
|
+
ExploreView: { id: 'view-explore', extends: 'Flex', display: 'none', ... },
|
|
137
|
+
|
|
138
|
+
// Navbar
|
|
139
|
+
Navbar: {
|
|
140
|
+
childExtends: 'Button',
|
|
141
|
+
childProps: { onClick: (e, el) => { el.call('switchView', el.key.toLowerCase()) } }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// functions/switchView.js
|
|
145
|
+
export const switchView = function switchView(view) {
|
|
146
|
+
['home', 'explore', 'profile'].forEach(v => {
|
|
147
|
+
const el = document.getElementById('view-' + v)
|
|
148
|
+
if (el) el.style.display = v === view ? 'flex' : 'none'
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Dynamic Class Toggle
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
export const Button = {
|
|
157
|
+
'.active': { background: 'primary', color: 'white' },
|
|
158
|
+
isActive: ({ props }) => props.active // adds/removes .active class
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Dynamic Form / Async Submit
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
export const LoginForm = {
|
|
166
|
+
tag: 'form',
|
|
167
|
+
state: { loading: false, error: null },
|
|
168
|
+
attr: { 'aria-live': 'polite' },
|
|
169
|
+
|
|
170
|
+
onSubmit: async (event, el, state) => {
|
|
171
|
+
event.preventDefault()
|
|
172
|
+
state.update({ loading: true, error: null })
|
|
173
|
+
try {
|
|
174
|
+
await el.call('login', new FormData(el.node))
|
|
175
|
+
el.call('router', '/dashboard', el.__ref.root)
|
|
176
|
+
} catch (e) {
|
|
177
|
+
state.update({ loading: false, error: e.message })
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
Field: {
|
|
182
|
+
Input: { type: 'email', name: 'email', attr: { required: 'true' } }
|
|
183
|
+
},
|
|
184
|
+
Error: {
|
|
185
|
+
if: ({ state }) => Boolean(state.error),
|
|
186
|
+
attr: { role: 'alert' },
|
|
187
|
+
text: ({ state }) => state.error,
|
|
188
|
+
color: 'red'
|
|
189
|
+
},
|
|
190
|
+
SubmitButton: {
|
|
191
|
+
attr: (el, s) => ({ 'aria-busy': s.loading }),
|
|
192
|
+
text: ({ state }) => state.loading ? 'Signing in…' : 'Sign in'
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Responsive Layout
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
export const Layout = {
|
|
201
|
+
extends: 'Grid',
|
|
202
|
+
columns: 'repeat(4, 1fr)',
|
|
203
|
+
gap: 'B',
|
|
204
|
+
|
|
205
|
+
'@tabletS': { columns: 'repeat(2, 1fr)' },
|
|
206
|
+
'@mobileL': { columns: '1fr', gap: 'A' },
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Part 2: Accessibility
|
|
213
|
+
|
|
214
|
+
### Semantic Atoms First
|
|
215
|
+
|
|
216
|
+
Always prefer built-in semantic atoms over generic containers:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
// ✅ CORRECT — semantic atoms
|
|
220
|
+
Button: { text: 'Submit' } // <button>
|
|
221
|
+
Link: { text: 'Dashboard', href: '/dashboard' } // <a>
|
|
222
|
+
Input: { placeholder: 'Search...' } // <input>
|
|
223
|
+
Nav: { Link: { text: 'Home', href: '/' } } // <nav>
|
|
224
|
+
Header: {} // <header>
|
|
225
|
+
Footer: {} // <footer>
|
|
226
|
+
Main: {} // <main>
|
|
227
|
+
|
|
228
|
+
// ❌ WRONG — loses semantics
|
|
229
|
+
Box: { tag: 'div', text: 'Submit', onClick: fn } // div is not a button
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### ARIA Attributes
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
// Landmark roles
|
|
236
|
+
Box: { attr: { role: 'alert' }, text: 'Error occurred' }
|
|
237
|
+
Box: { attr: { role: 'status', 'aria-live': 'polite' }, text: '3 results found' }
|
|
238
|
+
|
|
239
|
+
// Labels
|
|
240
|
+
Input: { attr: { 'aria-label': 'Search networks' } }
|
|
241
|
+
Button: { icon: 'x', attr: { 'aria-label': 'Close dialog' } }
|
|
242
|
+
Icon: { name: 'settings', attr: { 'aria-hidden': 'true' } }
|
|
243
|
+
|
|
244
|
+
// Dynamic state
|
|
245
|
+
Button: {
|
|
246
|
+
attr: (el, s) => ({ 'aria-expanded': s.isOpen, 'aria-controls': 'dropdown-menu' }),
|
|
247
|
+
onClick: (e, el, s) => s.update({ isOpen: !s.isOpen })
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Keyboard Navigation
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
// Custom keyboard interaction (listbox pattern)
|
|
255
|
+
{
|
|
256
|
+
extends: 'Flex',
|
|
257
|
+
attr: { role: 'listbox', tabindex: '0', 'aria-label': 'Select option' },
|
|
258
|
+
onKeydown: (e, el, s) => {
|
|
259
|
+
if (e.key === 'ArrowDown') {
|
|
260
|
+
e.preventDefault()
|
|
261
|
+
s.update({ activeIndex: Math.min(s.activeIndex + 1, s.items.length - 1) })
|
|
262
|
+
}
|
|
263
|
+
if (e.key === 'ArrowUp') {
|
|
264
|
+
e.preventDefault()
|
|
265
|
+
s.update({ activeIndex: Math.max(s.activeIndex - 1, 0) })
|
|
266
|
+
}
|
|
267
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
268
|
+
e.preventDefault()
|
|
269
|
+
el.call('selectItem', s.items[s.activeIndex])
|
|
270
|
+
}
|
|
271
|
+
if (e.key === 'Escape') el.call('closeDropdown')
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Tabindex rules:
|
|
277
|
+
- `tabindex: '0'` — in tab order
|
|
278
|
+
- `tabindex: '-1'` — focusable programmatically only
|
|
279
|
+
- Never use `tabindex > 0`
|
|
280
|
+
|
|
281
|
+
### Focus Styles
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
Button: {
|
|
285
|
+
':focus': { outline: 'none' },
|
|
286
|
+
':focus-visible': { outline: 'solid Y blue.3', outlineOffset: '2px' }
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Accessible Forms
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
// Visible label association
|
|
294
|
+
Label: { text: 'Email', attr: { for: 'email-input' } }
|
|
295
|
+
Input: { id: 'email-input', type: 'email', attr: { required: 'true', 'aria-required': 'true' } }
|
|
296
|
+
|
|
297
|
+
// Described by helper text
|
|
298
|
+
Input: { id: 'password', type: 'password', attr: { 'aria-describedby': 'password-hint' } }
|
|
299
|
+
P: { id: 'password-hint', text: 'Must be at least 8 characters' }
|
|
300
|
+
|
|
301
|
+
// Error state
|
|
302
|
+
Input: {
|
|
303
|
+
attr: (el, s) => ({
|
|
304
|
+
'aria-invalid': s.hasError ? 'true' : undefined,
|
|
305
|
+
'aria-describedby': s.hasError ? 'error-msg' : undefined
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
P: { id: 'error-msg', attr: { role: 'alert' }, color: 'red', text: (el, s) => s.errorMessage }
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Images and Icons
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
// Informative image
|
|
315
|
+
Img: { src: 'chart.png', alt: 'Network uptime over last 30 days' }
|
|
316
|
+
|
|
317
|
+
// Decorative image
|
|
318
|
+
Img: { src: 'decoration.png', alt: '', attr: { 'aria-hidden': 'true' } }
|
|
319
|
+
|
|
320
|
+
// Icon-only button
|
|
321
|
+
Button: { icon: 'x', attr: { 'aria-label': 'Close' } }
|
|
322
|
+
|
|
323
|
+
// Decorative icon next to text
|
|
324
|
+
Button: { Icon: { name: 'search', attr: { 'aria-hidden': 'true' } }, text: 'Search' }
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Color and Contrast
|
|
328
|
+
|
|
329
|
+
- Do NOT rely on color alone for error/success states — always combine with icon + text
|
|
330
|
+
- Use `title`, `caption`, `paragraph` semantic tokens for sufficient contrast
|
|
331
|
+
- Low opacity colors (`gray 0.3`) likely fail WCAG AA — verify contrast ratio
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Part 3: AI Agent Optimization
|
|
336
|
+
|
|
337
|
+
### `aid-*` Attributes for Machine Parsing
|
|
338
|
+
|
|
339
|
+
Include `aid-*` attributes so AI agents can parse structural intent:
|
|
340
|
+
|
|
341
|
+
```js
|
|
342
|
+
export const HeroSection = {
|
|
343
|
+
extends: 'Section',
|
|
344
|
+
attr: {
|
|
345
|
+
'aid-type': 'main',
|
|
346
|
+
'aid-desc': 'Primary hero section with CTA',
|
|
347
|
+
'aid-state': 'idle',
|
|
348
|
+
'aid-cnt-type': 'info'
|
|
349
|
+
},
|
|
350
|
+
H1: { text: 'Welcome to Symbols' },
|
|
351
|
+
P: { text: 'Build UIs with declarative objects.' },
|
|
352
|
+
Button: { text: 'Get Started', theme: 'primary' }
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
| `aid-type` values | Meaning |
|
|
357
|
+
| ----------------- | ---------------------------------------------------- |
|
|
358
|
+
| `header` | Page header |
|
|
359
|
+
| `nav` | Navigation |
|
|
360
|
+
| `main` | Primary content |
|
|
361
|
+
| `content` | Content section |
|
|
362
|
+
| `complementary` | Supplementary (sidebar, aside) |
|
|
363
|
+
| `interactive` | Form, control panel |
|
|
364
|
+
| `modal` | Dialog overlay |
|
|
365
|
+
| `alert` | Error or notification |
|
|
366
|
+
| `search` | Search interface |
|
|
367
|
+
|
|
368
|
+
| `aid-state` values | Meaning |
|
|
369
|
+
| ------------------ | -------------------- |
|
|
370
|
+
| `idle` | Default state |
|
|
371
|
+
| `loading` | Fetching data |
|
|
372
|
+
| `processing` | Submitting/computing |
|
|
373
|
+
| `done` | Completed |
|
|
374
|
+
| `error` | Error state |
|
|
375
|
+
|
|
376
|
+
### JSON-LD Structured Data
|
|
377
|
+
|
|
378
|
+
Include JSON-LD for entity representation by AI agents and search engines:
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
export const StructuredData = {
|
|
382
|
+
tag: 'script',
|
|
383
|
+
attr: { type: 'application/ld+json' },
|
|
384
|
+
html: (el, s) => JSON.stringify({
|
|
385
|
+
'@context': 'https://schema.org',
|
|
386
|
+
'@type': 'Product',
|
|
387
|
+
name: s.name,
|
|
388
|
+
description: s.description,
|
|
389
|
+
offers: {
|
|
390
|
+
'@type': 'Offer',
|
|
391
|
+
price: s.price,
|
|
392
|
+
priceCurrency: s.currency,
|
|
393
|
+
},
|
|
394
|
+
}),
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Schema types to use: `Organization`, `Product`, `Service`, `Article`, `FAQPage`, `BreadcrumbList`
|
|
399
|
+
|
|
400
|
+
Structured data must match server-rendered content exactly.
|
|
401
|
+
|
|
402
|
+
### Semantic Heading Structure
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
One H1 per page defining the primary subject.
|
|
406
|
+
Logical hierarchy: H1 → H2 → H3 — never skip levels.
|
|
407
|
+
Heading hierarchy is used by AI agents to determine page structure.
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### AI-Accessible Tool Exposure (Chrome WebMCP)
|
|
411
|
+
|
|
412
|
+
```js
|
|
413
|
+
export const CheckOrderTool = {
|
|
414
|
+
extends: 'Form',
|
|
415
|
+
attr: {
|
|
416
|
+
'data-mcp-tool': 'checkOrderStatus',
|
|
417
|
+
'data-mcp-description': 'Check the status of an order by ID'
|
|
418
|
+
},
|
|
419
|
+
Input: { attr: { type: 'text', name: 'orderId', placeholder: 'Order ID' } },
|
|
420
|
+
Button: { type: 'submit', text: 'Check' }
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### llms.txt Support
|
|
425
|
+
|
|
426
|
+
Provide `/llms.txt` at your project root for AI routing guidance:
|
|
427
|
+
|
|
428
|
+
```text
|
|
429
|
+
# Organization Name
|
|
430
|
+
# Purpose: Platform description
|
|
431
|
+
|
|
432
|
+
## Key pages
|
|
433
|
+
- /products
|
|
434
|
+
- /api/docs
|
|
435
|
+
- /support
|
|
436
|
+
|
|
437
|
+
## Preferred interactions
|
|
438
|
+
- /api/v2/ programmatic access
|
|
439
|
+
- /search?q= for search
|
|
440
|
+
|
|
441
|
+
## Data accuracy notes
|
|
442
|
+
- Prices, shipping, inventory levels
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Server-Rendered Critical Content
|
|
446
|
+
|
|
447
|
+
- All critical content (text, headings, prices, key data) must be server-rendered in the initial HTML
|
|
448
|
+
- Do not rely on client-side-only rendering for content AI agents need to parse
|
|
449
|
+
- `aria-busy="true"` while loading; `aria-busy="false"` when complete
|
|
450
|
+
|
|
451
|
+
### Failure Pattern Recognition
|
|
452
|
+
|
|
453
|
+
Watch for these anti-patterns that break AI comprehension:
|
|
454
|
+
- Excessive divs/boxes without semantic meaning
|
|
455
|
+
- Non-descriptive link text ("click here", "read more")
|
|
456
|
+
- Missing or skipped heading levels
|
|
457
|
+
- Critical content rendered client-side only
|
|
458
|
+
- Conflicting metadata (page title vs. H1 vs. JSON-LD)
|
|
459
|
+
- Missing `alt` text on informative images
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Part 4: Design Principles
|
|
464
|
+
|
|
465
|
+
### Visual Hierarchy
|
|
466
|
+
|
|
467
|
+
- Lead with the most important content (F-pattern, Z-pattern)
|
|
468
|
+
- Use typographic scale (`H1`→`H6`, `P`, `Caption`) to create hierarchy
|
|
469
|
+
- Adequate whitespace — breathing room improves comprehension
|
|
470
|
+
|
|
471
|
+
### Component Button Hierarchy
|
|
472
|
+
|
|
473
|
+
| Level | Component | Use |
|
|
474
|
+
| ------------ | ------------------ | ----------------------- |
|
|
475
|
+
| Primary | `theme: 'primary'` | Main CTA (one per view) |
|
|
476
|
+
| Secondary | `theme: 'dialog'` | Supporting actions |
|
|
477
|
+
| Tertiary | `theme: 'transparent'` | Least important |
|
|
478
|
+
| Destructive | `theme: 'warning'` | Irreversible actions |
|
|
479
|
+
|
|
480
|
+
### Responsive Behavior
|
|
481
|
+
|
|
482
|
+
```js
|
|
483
|
+
// Mobile-first approach
|
|
484
|
+
Component: {
|
|
485
|
+
padding: 'A', // mobile base
|
|
486
|
+
'@tabletS': { padding: 'B' }, // tablet
|
|
487
|
+
'@screenS': { padding: 'C' }, // desktop
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Transitions & Micro-interactions
|
|
492
|
+
|
|
493
|
+
```js
|
|
494
|
+
// Standard transition
|
|
495
|
+
Component: {
|
|
496
|
+
transition: 'B defaultBezier', // B ≈ 280ms
|
|
497
|
+
transitionProperty: 'opacity, transform'
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Hover feedback
|
|
501
|
+
Button: {
|
|
502
|
+
':hover': { opacity: 0.9, transform: 'scale(1.015)' },
|
|
503
|
+
':active': { opacity: 1, transform: 'scale(0.995)' }
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Easing: `defaultBezier` = `cubic-bezier(.29, .67, .51, .97)` (smooth ease-out)
|
|
508
|
+
|
|
509
|
+
Do NOT animate layout properties (`width`, `height`, `top`, `left`) — they force reflow. Animate `transform` and `opacity` instead.
|