@myop/cli 0.1.40 → 0.1.41

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.
@@ -0,0 +1,233 @@
1
+ ---
2
+ name: myop-component
3
+ description: "Myop is a platform for creating self-contained HTML UI components that integrate into any host application. This skill covers the component public API (myop_init_interface, myop_cta_handler), type definitions, sizing, layout, development workflow, and CLI commands. When building or modifying a Myop component, you must learn this skill."
4
+ ---
5
+
6
+ # Myop Component Developer
7
+
8
+ Build high-performance, self-contained HTML components for the Myop platform.
9
+
10
+ ## Immediate Action Required - Read This First
11
+
12
+ This skill activates when a `myop.config.json` file exists in the project, or when the user mentions "myop", "myop component", or asks to create/edit an HTML component for Myop.
13
+
14
+ **Your first action MUST be:**
15
+ 1. Check if `myop.config.json` exists in the current directory
16
+ 2. If **YES** (existing component):
17
+ - Read `myop.config.json` to understand the component name and state
18
+ - Read `index.html` to understand the current component implementation
19
+ - Implement the requested changes following the patterns in this skill
20
+ 3. If **NO** (new component):
21
+ - Guide the user to run `myop create` to scaffold a new component
22
+ - Or create the files manually following the structure below
23
+
24
+ ## Component Architecture
25
+
26
+ Every Myop component is a **single HTML file** (`index.html` or `dist/index.html`) that communicates with a host application through exactly **2 global functions**:
27
+
28
+ | Function | Direction | Purpose | Reference |
29
+ |----------|-----------|---------|-----------|
30
+ | `myop_init_interface(data)` | Host -> Component | Receive data, render UI | [component-api.md](references/component-api.md) |
31
+ | `myop_cta_handler(action, payload)` | Component -> Host | Send user actions back | [component-api.md](references/component-api.md) |
32
+
33
+ ## CRITICAL: Rules You Must Follow
34
+
35
+ 1. **Both functions MUST be defined at top-level script execution** - never inside `setTimeout`, `Promise.then`, `async`, `DOMContentLoaded`, or any deferred code
36
+ 2. **Rendering MUST be synchronous** - no `fetch`, no `await`, no `setTimeout` inside `myop_init_interface`
37
+ 3. **All state is private** - use an IIFE to encapsulate; only expose the 2 global functions
38
+ 4. **Single-file output** - the final deployed artifact is ONE HTML file with all CSS/JS inlined
39
+ 5. **No external network requests** - components cannot fetch external resources at runtime
40
+
41
+ ## Quick Start - Minimal Component
42
+
43
+ ```html
44
+ <!DOCTYPE html>
45
+ <html lang="en">
46
+ <head>
47
+ <meta charset="UTF-8">
48
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
49
+ <meta name="myop:size" content='{"width":"100%","height":"100%"}'>
50
+ <title>My Component</title>
51
+ <script type="myop/types">
52
+ interface MyopInitData {
53
+ title: string;
54
+ items: Array<{ id: string; label: string }>;
55
+ }
56
+
57
+ interface MyopCtaPayloads {
58
+ 'item-selected': { itemId: string };
59
+ 'size-requested': {
60
+ width?: number | null;
61
+ height?: number | null;
62
+ minWidth?: number | null;
63
+ maxWidth?: number | null;
64
+ minHeight?: number | null;
65
+ maxHeight?: number | null;
66
+ required?: boolean;
67
+ };
68
+ }
69
+
70
+ declare function myop_init_interface(): MyopInitData;
71
+ declare function myop_init_interface(data: MyopInitData): void;
72
+ declare function myop_cta_handler<K extends keyof MyopCtaPayloads>(
73
+ action: K, payload: MyopCtaPayloads[K]
74
+ ): void;
75
+ </script>
76
+ <style>
77
+ * { box-sizing: border-box; margin: 0; padding: 0; }
78
+ html, body { width: 100%; height: 100%; overflow: hidden;
79
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
80
+ }
81
+ #app-root { width: 100%; height: 100%; padding: 16px; }
82
+ </style>
83
+ </head>
84
+ <body>
85
+ <div id="app-root"></div>
86
+
87
+ <script>
88
+ (function() {
89
+ var state = {};
90
+
91
+ function render(data) {
92
+ state = data;
93
+ var root = document.getElementById('app-root');
94
+ root.innerHTML =
95
+ '<h2>' + (data.title || '') + '</h2>' +
96
+ '<ul>' + (data.items || []).map(function(item) {
97
+ return '<li data-id="' + item.id + '">' + item.label + '</li>';
98
+ }).join('') + '</ul>';
99
+ }
100
+
101
+ window.myop_init_interface = function(data) {
102
+ if (!data) return state;
103
+ render(data);
104
+ };
105
+
106
+ window.myop_cta_handler = function(action, payload) {
107
+ // Overridden by host application
108
+ };
109
+
110
+ // Event delegation
111
+ document.getElementById('app-root').addEventListener('click', function(e) {
112
+ var li = e.target.closest('li');
113
+ if (li) {
114
+ window.myop_cta_handler('item-selected', { itemId: li.dataset.id });
115
+ }
116
+ });
117
+ })();
118
+ </script>
119
+
120
+ <script id="myop_preview">
121
+ window.myop_init_interface({
122
+ title: 'My Component',
123
+ items: [
124
+ { id: '1', label: 'First item' },
125
+ { id: '2', label: 'Second item' }
126
+ ]
127
+ });
128
+ </script>
129
+ </body>
130
+ </html>
131
+ ```
132
+
133
+ ## Component File Structure
134
+
135
+ ### Single-file mode (simplest)
136
+ ```
137
+ project/
138
+ ├── index.html # The entire component in one file
139
+ ├── myop.config.json # Component metadata
140
+ └── .gitignore
141
+ ```
142
+
143
+ ### Multi-file mode (with build step)
144
+ ```
145
+ project/
146
+ ├── index.html # HTML template with <link> and <script src>
147
+ ├── src/
148
+ │ ├── index.js # Entry point
149
+ │ ├── modules/
150
+ │ │ ├── app.js # Application logic
151
+ │ │ └── myop.js # Myop interface setup
152
+ │ └── styles/
153
+ │ ├── index.css # Style entry point
154
+ │ └── main.css # Main styles
155
+ ├── build.js # esbuild bundler (inlines JS/CSS into dist/index.html)
156
+ ├── dist/
157
+ │ └── index.html # Built single-file output
158
+ ├── myop.config.json
159
+ ├── package.json
160
+ └── .gitignore
161
+ ```
162
+
163
+ ### myop.config.json
164
+ ```json
165
+ {
166
+ "name": "Component Name",
167
+ "componentId": "DEV",
168
+ "type": "html",
169
+ "author": "@myop-cli",
170
+ "HMR": true
171
+ }
172
+ ```
173
+
174
+ - `componentId` starts as `"DEV"` and gets replaced with a real UUID after first `myop push`
175
+ - `organization` is added automatically after first push
176
+
177
+ ## Reference Documentation
178
+
179
+ | Topic | Reference |
180
+ |-------|-----------|
181
+ | Public API (myop_init_interface, myop_cta_handler) | [component-api.md](references/component-api.md) |
182
+ | Type Definitions (MyopInitData, MyopCtaPayloads) | [type-definitions.md](references/type-definitions.md) |
183
+ | Sizing, Layout & Responsive Design | [sizing-and-layout.md](references/sizing-and-layout.md) |
184
+ | CLI Commands & Dev Workflow | [dev-workflow.md](references/dev-workflow.md) |
185
+ | Performance & Best Practices | [best-practices.md](references/best-practices.md) |
186
+
187
+ ## Common Mistakes - WRONG vs CORRECT
188
+
189
+ ### Function Definition Timing
190
+
191
+ | WRONG | CORRECT |
192
+ |-------|---------|
193
+ | Defining inside `DOMContentLoaded` | Defining in top-level `<script>` |
194
+ | Defining inside `setTimeout` | Defining in synchronous IIFE |
195
+ | Defining inside `async` function | Defining before any async code |
196
+ | Defining in a module with `export` | Assigning directly to `window` |
197
+
198
+ ### Rendering
199
+
200
+ | WRONG | CORRECT |
201
+ |-------|---------|
202
+ | `await fetch()` inside `myop_init_interface` | Pure synchronous DOM manipulation |
203
+ | `setTimeout(() => render())` | Immediate `innerHTML` assignment |
204
+ | Multiple `appendChild` calls | Single `innerHTML` write |
205
+ | `document.createElement` loops | Template literal string building |
206
+
207
+ ### State Management
208
+
209
+ | WRONG | CORRECT |
210
+ |-------|---------|
211
+ | Global variables on `window` | Variables inside IIFE closure |
212
+ | Mutating data passed to `myop_init_interface` | Storing a copy of the data |
213
+ | Not returning state when called without args | `if (!data) return state;` pattern |
214
+
215
+ ## CLI Commands Quick Reference
216
+
217
+ | Command | Description |
218
+ |---------|-------------|
219
+ | `myop create` | Create a new component (scaffolds files + starts dev server) |
220
+ | `myop dev` | Start development server with HMR on port 9292 |
221
+ | `myop push` | Upload component to Myop platform |
222
+ | `myop sync` | Build and upload component to Myop platform |
223
+ | `myop login` | Authenticate with Myop (opens browser) |
224
+ | `myop logout` | Clear stored credentials |
225
+ | `myop whoami` | Show current authenticated user |
226
+
227
+ ## Workflow Summary
228
+
229
+ 1. `myop create` - Scaffold a new component
230
+ 2. Edit `index.html` - Build your component UI
231
+ 3. `myop dev` - Preview with hot reload at http://localhost:9292
232
+ 4. `myop push` - Upload to Myop platform
233
+ 5. View on dashboard: `https://dashboard.myop.dev/dashboard/2.0/component/<componentId>`
@@ -0,0 +1,406 @@
1
+ # Performance & Best Practices
2
+
3
+ Myop components must render instantly within a single event loop tick. This document covers patterns for achieving high performance and avoiding common pitfalls.
4
+
5
+ ## Contents
6
+ - [Performance Requirements](#performance-requirements)
7
+ - [Rendering Patterns](#rendering-patterns)
8
+ - [State Management](#state-management)
9
+ - [Event Handling](#event-handling)
10
+ - [CSS Patterns](#css-patterns)
11
+ - [Accessibility](#accessibility)
12
+ - [Anti-Patterns](#anti-patterns)
13
+
14
+ ## Performance Requirements
15
+
16
+ 1. **Synchronous rendering** - `myop_init_interface(data)` must update the DOM and return before yielding to the event loop
17
+ 2. **No network requests** - Components cannot fetch external resources
18
+ 3. **Single DOM write** - Build the entire HTML string, then set `innerHTML` once
19
+ 4. **Minimal reflows** - Avoid reading layout properties (offsetHeight, getBoundingClientRect) between writes
20
+
21
+ ## Rendering Patterns
22
+
23
+ ### Template Literal Rendering (recommended)
24
+
25
+ ```javascript
26
+ function render(data) {
27
+ var root = document.getElementById('app-root');
28
+ root.innerHTML =
29
+ '<div class="header">' +
30
+ '<h2>' + escapeHtml(data.title) + '</h2>' +
31
+ '<span class="count">' + data.items.length + ' items</span>' +
32
+ '</div>' +
33
+ '<ul class="list">' +
34
+ data.items.map(function(item) {
35
+ return '<li class="item' + (item.active ? ' active' : '') + '"' +
36
+ ' data-id="' + item.id + '">' +
37
+ escapeHtml(item.name) +
38
+ '</li>';
39
+ }).join('') +
40
+ '</ul>';
41
+ }
42
+
43
+ function escapeHtml(str) {
44
+ if (!str) return '';
45
+ return String(str)
46
+ .replace(/&/g, '&amp;')
47
+ .replace(/</g, '&lt;')
48
+ .replace(/>/g, '&gt;')
49
+ .replace(/"/g, '&quot;');
50
+ }
51
+ ```
52
+
53
+ ### DOM Caching
54
+
55
+ Cache element references that don't change between renders:
56
+
57
+ ```javascript
58
+ (function() {
59
+ // Cache once - these elements are in the static HTML
60
+ var root = document.getElementById('app-root');
61
+ var header = document.querySelector('.header');
62
+ var list = document.querySelector('.list');
63
+
64
+ function render(data) {
65
+ // Only update the parts that change
66
+ header.textContent = data.title;
67
+ list.innerHTML = data.items.map(function(item) {
68
+ return '<li data-id="' + item.id + '">' + item.name + '</li>';
69
+ }).join('');
70
+ }
71
+
72
+ // ...
73
+ })();
74
+ ```
75
+
76
+ ### Conditional Rendering
77
+
78
+ ```javascript
79
+ function render(data) {
80
+ var html = '<div class="container">';
81
+
82
+ if (data.loading) {
83
+ html += '<div class="spinner">Loading...</div>';
84
+ } else if (data.error) {
85
+ html += '<div class="error">' + escapeHtml(data.error) + '</div>';
86
+ } else if (data.items.length === 0) {
87
+ html += '<div class="empty">No items found</div>';
88
+ } else {
89
+ html += renderItemList(data.items);
90
+ }
91
+
92
+ html += '</div>';
93
+ document.getElementById('app-root').innerHTML = html;
94
+ }
95
+ ```
96
+
97
+ ## State Management
98
+
99
+ ### IIFE Encapsulation (required)
100
+
101
+ All component state and logic MUST be encapsulated in an IIFE (Immediately Invoked Function Expression):
102
+
103
+ ```javascript
104
+ (function() {
105
+ // Private state - not accessible outside this closure
106
+ var state = {};
107
+ var selectedId = null;
108
+ var isExpanded = false;
109
+
110
+ // Private functions
111
+ function render(data) { /* ... */ }
112
+ function updateSelection(id) { /* ... */ }
113
+
114
+ // Only expose the 2 required globals
115
+ window.myop_init_interface = function(data) {
116
+ if (!data) return state;
117
+ state = data;
118
+ render(data);
119
+ };
120
+
121
+ window.myop_cta_handler = function(action, payload) {};
122
+ })();
123
+ ```
124
+
125
+ ### Derived State
126
+
127
+ Compute derived values during render, not on every access:
128
+
129
+ ```javascript
130
+ function render(data) {
131
+ // Compute derived state once during render
132
+ var completedCount = data.tasks.filter(function(t) { return t.completed; }).length;
133
+ var pendingCount = data.tasks.length - completedCount;
134
+ var progress = data.tasks.length > 0
135
+ ? Math.round((completedCount / data.tasks.length) * 100)
136
+ : 0;
137
+
138
+ root.innerHTML =
139
+ '<div class="progress">' + progress + '% complete (' + completedCount + '/' + data.tasks.length + ')</div>' +
140
+ '<div class="list">' + renderTasks(data.tasks) + '</div>';
141
+ }
142
+ ```
143
+
144
+ ### Local UI State
145
+
146
+ For state that doesn't come from the host (e.g., expanded/collapsed sections, active tab):
147
+
148
+ ```javascript
149
+ (function() {
150
+ var state = {};
151
+ var uiState = {
152
+ activeTab: 'all',
153
+ expandedSections: {}
154
+ };
155
+
156
+ function render(data) {
157
+ state = data;
158
+ var filtered = filterByTab(data.items, uiState.activeTab);
159
+ root.innerHTML = renderTabs(uiState.activeTab) + renderList(filtered);
160
+ }
161
+
162
+ function setTab(tab) {
163
+ uiState.activeTab = tab;
164
+ render(state); // Re-render with same data, different UI state
165
+ }
166
+
167
+ // ...
168
+ })();
169
+ ```
170
+
171
+ ## Event Handling
172
+
173
+ ### Event Delegation (required for dynamic content)
174
+
175
+ Never attach event listeners to dynamically created elements. Use event delegation on a static parent:
176
+
177
+ ```javascript
178
+ (function() {
179
+ var root = document.getElementById('app-root');
180
+
181
+ // Single listener handles all interactions
182
+ root.addEventListener('click', function(e) {
183
+ // Check what was clicked using closest()
184
+ var item = e.target.closest('.item');
185
+ if (item) {
186
+ window.myop_cta_handler('item-clicked', {
187
+ itemId: item.dataset.id
188
+ });
189
+ return;
190
+ }
191
+
192
+ var deleteBtn = e.target.closest('.delete-btn');
193
+ if (deleteBtn) {
194
+ e.stopPropagation(); // Don't trigger item click
195
+ window.myop_cta_handler('item-deleted', {
196
+ itemId: deleteBtn.closest('.item').dataset.id
197
+ });
198
+ return;
199
+ }
200
+
201
+ var tab = e.target.closest('.tab');
202
+ if (tab) {
203
+ setTab(tab.dataset.tab);
204
+ return;
205
+ }
206
+ });
207
+
208
+ // Input events
209
+ root.addEventListener('change', function(e) {
210
+ if (e.target.matches('.checkbox')) {
211
+ var item = e.target.closest('.item');
212
+ window.myop_cta_handler('item-toggled', {
213
+ itemId: item.dataset.id,
214
+ checked: e.target.checked
215
+ });
216
+ }
217
+ });
218
+ })();
219
+ ```
220
+
221
+ ### Keyboard Support
222
+
223
+ ```javascript
224
+ root.addEventListener('keydown', function(e) {
225
+ if (e.key === 'Enter' || e.key === ' ') {
226
+ var focusable = e.target.closest('[role="button"], .item');
227
+ if (focusable) {
228
+ e.preventDefault();
229
+ focusable.click(); // Reuse click handler
230
+ }
231
+ }
232
+ });
233
+ ```
234
+
235
+ ## CSS Patterns
236
+
237
+ ### Scoped Styles
238
+
239
+ Since the component runs in an iframe, global styles are safe. But for clarity, scope to `#app-root`:
240
+
241
+ ```css
242
+ #app-root .header { /* ... */ }
243
+ #app-root .item { /* ... */ }
244
+ #app-root .item:hover { /* ... */ }
245
+ #app-root .item.active { /* ... */ }
246
+ ```
247
+
248
+ ### Responsive Design
249
+
250
+ Components should adapt to their container size. Use relative units and flexible layouts:
251
+
252
+ ```css
253
+ #app-root {
254
+ display: flex;
255
+ flex-direction: column;
256
+ height: 100%;
257
+ font-size: 14px;
258
+ }
259
+
260
+ /* Stack horizontally when wide enough */
261
+ @media (min-width: 500px) {
262
+ .content {
263
+ flex-direction: row;
264
+ }
265
+ }
266
+
267
+ /* Compact mode for narrow containers */
268
+ @media (max-width: 300px) {
269
+ .header { padding: 8px; font-size: 12px; }
270
+ .item { padding: 6px 8px; }
271
+ }
272
+ ```
273
+
274
+ ### Theme Support
275
+
276
+ Accept theme via data and apply with CSS classes:
277
+
278
+ ```javascript
279
+ function render(data) {
280
+ document.documentElement.setAttribute('data-theme', data.config?.theme || 'light');
281
+ // ... render content
282
+ }
283
+ ```
284
+
285
+ ```css
286
+ :root {
287
+ --bg: #ffffff;
288
+ --text: #333333;
289
+ --border: #e0e0e0;
290
+ }
291
+
292
+ [data-theme="dark"] {
293
+ --bg: #1a1a2e;
294
+ --text: #e0e0e0;
295
+ --border: #333355;
296
+ }
297
+
298
+ #app-root {
299
+ background: var(--bg);
300
+ color: var(--text);
301
+ }
302
+ ```
303
+
304
+ ## Accessibility
305
+
306
+ ### Semantic HTML
307
+
308
+ ```html
309
+ <!-- Use semantic elements -->
310
+ <nav class="tabs" role="tablist">...</nav>
311
+ <main class="content" role="tabpanel">...</main>
312
+
313
+ <!-- Use ARIA for custom widgets -->
314
+ <div role="listbox" aria-label="Task list">
315
+ <div role="option" aria-selected="true">...</div>
316
+ </div>
317
+ ```
318
+
319
+ ### Focus Management
320
+
321
+ ```css
322
+ /* Visible focus indicators */
323
+ :focus-visible {
324
+ outline: 2px solid #4a90d9;
325
+ outline-offset: 2px;
326
+ }
327
+
328
+ /* Interactive elements should be focusable */
329
+ .item[tabindex="0"] { cursor: pointer; }
330
+ ```
331
+
332
+ ## Anti-Patterns
333
+
334
+ ### Never: Async operations in myop_init_interface
335
+
336
+ ```javascript
337
+ // WRONG
338
+ window.myop_init_interface = async function(data) {
339
+ const extra = await loadMoreData(); // NO
340
+ render(data, extra);
341
+ };
342
+
343
+ // WRONG
344
+ window.myop_init_interface = function(data) {
345
+ fetch('/api/data').then(render); // NO
346
+ setTimeout(() => render(data), 0); // NO
347
+ requestAnimationFrame(() => render(data)); // NO
348
+ };
349
+ ```
350
+
351
+ ### Never: Multiple DOM writes
352
+
353
+ ```javascript
354
+ // WRONG - 3 reflows
355
+ root.innerHTML = '<div class="header"></div>';
356
+ root.querySelector('.header').textContent = data.title;
357
+ root.innerHTML += '<ul>' + listHtml + '</ul>';
358
+
359
+ // CORRECT - 1 DOM write
360
+ root.innerHTML =
361
+ '<div class="header">' + data.title + '</div>' +
362
+ '<ul>' + listHtml + '</ul>';
363
+ ```
364
+
365
+ ### Never: Global variables
366
+
367
+ ```javascript
368
+ // WRONG - pollutes global scope
369
+ var myState = {};
370
+ window.myHelper = function() {};
371
+
372
+ // CORRECT - everything inside IIFE
373
+ (function() {
374
+ var myState = {};
375
+ function myHelper() {}
376
+ // ...
377
+ })();
378
+ ```
379
+
380
+ ### Never: Direct DOM creation in loops
381
+
382
+ ```javascript
383
+ // WRONG - O(n) reflows
384
+ data.items.forEach(function(item) {
385
+ var li = document.createElement('li');
386
+ li.textContent = item.name;
387
+ list.appendChild(li); // Reflow on each append
388
+ });
389
+
390
+ // CORRECT - single innerHTML write
391
+ list.innerHTML = data.items.map(function(item) {
392
+ return '<li>' + escapeHtml(item.name) + '</li>';
393
+ }).join('');
394
+ ```
395
+
396
+ ### Never: External resource loading
397
+
398
+ ```javascript
399
+ // WRONG - components can't fetch
400
+ var img = new Image();
401
+ img.src = 'https://external-cdn.com/image.png'; // NO
402
+
403
+ // WRONG - loading external scripts
404
+ var script = document.createElement('script');
405
+ script.src = 'https://cdn.example.com/lib.js'; // NO
406
+ ```