@jay-framework/jay-stack-cli 0.15.4 → 0.15.6

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 (36) hide show
  1. package/agent-kit-template/{INSTRUCTIONS.md → designer/INSTRUCTIONS.md} +11 -8
  2. package/agent-kit-template/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +1 -0
  3. package/agent-kit-template/{jay-html-syntax.md → designer/jay-html-components.md} +89 -158
  4. package/agent-kit-template/designer/jay-html-styling.md +97 -0
  5. package/agent-kit-template/designer/jay-html-syntax.md +44 -0
  6. package/agent-kit-template/designer/jay-html-template-syntax.md +203 -0
  7. package/agent-kit-template/developer/INSTRUCTIONS.md +34 -0
  8. package/agent-kit-template/developer/cli-commands.md +228 -0
  9. package/agent-kit-template/developer/component-data.md +109 -0
  10. package/agent-kit-template/developer/component-refs.md +117 -0
  11. package/agent-kit-template/developer/component-state.md +140 -0
  12. package/agent-kit-template/developer/configuration.md +76 -0
  13. package/agent-kit-template/developer/page-components.md +103 -0
  14. package/agent-kit-template/developer/page-contracts.md +114 -0
  15. package/agent-kit-template/developer/project-structure.md +242 -0
  16. package/agent-kit-template/developer/render-results.md +112 -0
  17. package/agent-kit-template/developer/routing.md +161 -0
  18. package/agent-kit-template/developer/seo-guide.md +93 -0
  19. package/agent-kit-template/plugin/INSTRUCTIONS.md +40 -0
  20. package/agent-kit-template/plugin/actions-guide.md +125 -0
  21. package/agent-kit-template/plugin/component-context.md +103 -0
  22. package/agent-kit-template/plugin/component-data.md +109 -0
  23. package/agent-kit-template/plugin/component-refs.md +117 -0
  24. package/agent-kit-template/plugin/component-state.md +140 -0
  25. package/agent-kit-template/plugin/component-structure.md +174 -0
  26. package/agent-kit-template/plugin/contracts-guide.md +193 -0
  27. package/agent-kit-template/plugin/plugin-structure.md +194 -0
  28. package/agent-kit-template/plugin/render-results.md +112 -0
  29. package/agent-kit-template/plugin/seo-guide.md +93 -0
  30. package/agent-kit-template/plugin/services-guide.md +116 -0
  31. package/agent-kit-template/plugin/validation.md +101 -0
  32. package/dist/index.js +805 -61
  33. package/package.json +10 -10
  34. /package/agent-kit-template/{cli-commands.md → designer/cli-commands.md} +0 -0
  35. /package/agent-kit-template/{project-structure.md → designer/project-structure.md} +0 -0
  36. /package/agent-kit-template/{routing.md → designer/routing.md} +0 -0
@@ -0,0 +1,203 @@
1
+ # Jay-HTML Template Syntax
2
+
3
+ ## File Structure
4
+
5
+ A `.jay-html` file is standard HTML with jay-specific extensions.
6
+
7
+ ```html
8
+ <html>
9
+ <head>
10
+ <!-- Page contract (optional — defines page-level data) -->
11
+ <script type="application/jay-data" contract="./page.jay-contract"></script>
12
+
13
+ <!-- Explicit route params (for static override routes) -->
14
+ <script type="application/jay-params">
15
+ slug: ceramic-flower-vase
16
+ </script>
17
+
18
+ <!-- Headless component imports -->
19
+ <script type="application/jay-headless" plugin="..." contract="..." key="..."></script>
20
+
21
+ <!-- Headfull component imports -->
22
+ <script type="application/jay-headfull" src="..." names="..." contract="..."></script>
23
+
24
+ <!-- Styles -->
25
+ <style>
26
+ /* inline CSS */
27
+ </style>
28
+ <link rel="stylesheet" href="../../styles/theme.css" />
29
+ </head>
30
+ <body>
31
+ <!-- Template with data bindings -->
32
+ <h1>{title}</h1>
33
+ </body>
34
+ </html>
35
+ ```
36
+
37
+ ## Data Binding
38
+
39
+ Use `{expression}` to bind contract data:
40
+
41
+ ```html
42
+ <h1>{productName}</h1>
43
+ <!-- simple -->
44
+ <span>{product.price}</span>
45
+ <!-- nested via key -->
46
+ <div style="color: {textColor}">{msg}</div>
47
+ <!-- in attributes -->
48
+ <a href="/products/{slug}">{name}</a>
49
+ <!-- interpolated in attr values -->
50
+ ```
51
+
52
+ ## Boolean Attributes
53
+
54
+ HTML boolean attributes (`disabled`, `checked`, `hidden`, `readonly`) can be bound to contract data:
55
+
56
+ ```html
57
+ <button disabled="isSubmitting">Submit</button>
58
+ <!-- disabled when isSubmitting is true -->
59
+ <button disabled="!inStock">Add to Cart</button>
60
+ <!-- disabled when inStock is false -->
61
+ <input type="checkbox" checked="isSelected" />
62
+ <!-- checked when isSelected is true -->
63
+ <div hidden="!isVisible">Content</div>
64
+ <!-- hidden when isVisible is false -->
65
+ ```
66
+
67
+ - Set the attribute value to a **boolean tag name** — the attribute is present when true, absent when false
68
+ - Use `!` prefix to negate: `disabled="!enabled"` means disabled when enabled is false
69
+ - Without a value (`disabled` alone), the attribute is always present (standard HTML behavior)
70
+
71
+ ## Conditional Rendering
72
+
73
+ Use the `if` attribute to conditionally show elements.
74
+
75
+ ### Boolean
76
+
77
+ ```html
78
+ <span if="inStock">In Stock</span> <span if="!inStock">Out of Stock</span>
79
+ ```
80
+
81
+ ### Enum Variant
82
+
83
+ No quotes around the value:
84
+
85
+ ```html
86
+ <div if="type===physical">Ships to your door</div>
87
+ <div if="type!==physical">Not a physical product</div>
88
+ <div if="status===active">Active</div>
89
+ ```
90
+
91
+ ### Numeric Comparisons
92
+
93
+ Compare against numbers or other fields:
94
+
95
+ ```html
96
+ <span if="count > 0">You have {count} items</span>
97
+ <span if="count <= 0">No items</span>
98
+ <button if="currentPage <= 1" disabled>Previous</button>
99
+ <span if="price <= budget">Affordable</span>
100
+ <span if="available >= required">In stock</span>
101
+ ```
102
+
103
+ Operators: `>`, `<`, `>=`, `<=`, `==`, `!=`
104
+
105
+ ### Logical AND / OR
106
+
107
+ Combine conditions with `&&` and `||`:
108
+
109
+ ```html
110
+ <div if="inStock && hasDiscount">Great deal!</div>
111
+ <span if="isPromoted || hasDiscount">Has offer</span>
112
+ ```
113
+
114
+ Use parentheses for complex expressions:
115
+
116
+ ```html
117
+ <div if="(inStock && hasDiscount) || isPromoted">Buyable</div>
118
+ <div if="inStock && price > 0">Purchasable</div>
119
+ <button if="count <= 0 || isLoading" disabled>Checkout</button>
120
+ ```
121
+
122
+ ### Rules Summary
123
+
124
+ - Boolean: `if="flag"` / `if="!flag"`
125
+ - Enum: `if="tag===value"` / `if="tag!==value"` (no quotes around value)
126
+ - Numeric: `if="count > 0"`, `if="price <= budget"`
127
+ - Field comparison: `if="available >= required"`
128
+ - Logical: `if="a && b"`, `if="a || b"`, `if="(a || b) && c"`
129
+ - Negation: `!` prefix on booleans
130
+
131
+ ## Loops (forEach / trackBy)
132
+
133
+ Iterate over repeated sub-contracts:
134
+
135
+ ```html
136
+ <li forEach="products" trackBy="id">
137
+ <a href="/products/{slug}">
138
+ <div>{name}</div>
139
+ <div>{price}</div>
140
+ </a>
141
+ </li>
142
+ ```
143
+
144
+ - `forEach` — the repeated tag name from the contract
145
+ - `trackBy` — stable unique key for each item (must match contract's trackBy)
146
+ - Inside the loop, bindings resolve to the **current item's** tags
147
+
148
+ **Nested loops:**
149
+
150
+ ```html
151
+ <div forEach="options" trackBy="_id">
152
+ <h3>{name}</h3>
153
+ <div forEach="choices" trackBy="choiceId">
154
+ <button ref="choiceButton">{name}</button>
155
+ </div>
156
+ </div>
157
+ ```
158
+
159
+ ## Refs (Interactions)
160
+
161
+ Map elements to contract `interactive` tags using `ref`:
162
+
163
+ ```html
164
+ <button ref="addToCart">Add to Cart</button>
165
+ <input value="{quantity}" ref="quantityInput" />
166
+ <a ref="productLink" href="/products/{slug}">{name}</a>
167
+ <select ref="sizeSelector">
168
+ ...
169
+ </select>
170
+ ```
171
+
172
+ Match the element type to the contract's `elementType`:
173
+
174
+ - `HTMLButtonElement` → `<button>`
175
+ - `HTMLInputElement` → `<input>`
176
+ - `HTMLAnchorElement` → `<a>`
177
+ - `HTMLSelectElement` → `<select>`
178
+
179
+ **Key-based headless refs** — prefix with the key:
180
+
181
+ ```html
182
+ <button ref="rating.submitButton">Submit</button> <button ref="mt.happy">+1 Happy</button>
183
+ ```
184
+
185
+ **Refs inside forEach** — use the tag path from the contract:
186
+
187
+ ```html
188
+ <div forEach="options" trackBy="_id">
189
+ <div forEach="choices" trackBy="choiceId">
190
+ <button ref="choiceButton">{name}</button>
191
+ </div>
192
+ </div>
193
+ ```
194
+
195
+ ## Page-Level Contract
196
+
197
+ A page can define its own data contract:
198
+
199
+ ```html
200
+ <script type="application/jay-data" contract="./page.jay-contract"></script>
201
+ ```
202
+
203
+ Tags from the page contract are bound directly (no key prefix).
@@ -0,0 +1,34 @@
1
+ # Jay Stack Developer — Agent Kit
2
+
3
+ This folder contains guides for building jay-stack projects: project configuration, routing, page components, and wiring plugins together.
4
+
5
+ ## What Does the Developer Role Do?
6
+
7
+ The developer sets up the project, configures plugins, creates page-level components (`page.ts`), defines page contracts (`page.jay-contract`), and wires everything together. This is distinct from the designer role (creates jay-html UI) and the plugin role (creates reusable headless components).
8
+
9
+ ## Workflow
10
+
11
+ 1. **Set up the project** — `jay-stack setup` to configure plugins
12
+ 2. **Define routes** — create page directories under `src/pages/`
13
+ 3. **Create page contracts** — `page.jay-contract` for page-level data
14
+ 4. **Create page components** — `page.ts` with `makeJayStackComponent`
15
+ 5. **Configure services** — `src/init.ts` for project-level services
16
+ 6. **Validate** — `jay-stack validate`
17
+ 7. **Test** — `jay-stack dev --test-mode`
18
+
19
+ ## Guides
20
+
21
+ | File | Topic |
22
+ | -------------------------------------------- | ---------------------------------------------------------- |
23
+ | [project-structure.md](project-structure.md) | Directory layout, configuration files |
24
+ | [routing.md](routing.md) | Directory-based routing, dynamic routes |
25
+ | [configuration.md](configuration.md) | .jay file, plugin config, init.ts |
26
+ | [page-contracts.md](page-contracts.md) | Page-level contracts (page.jay-contract) |
27
+ | [page-components.md](page-components.md) | page.ts: makeJayStackComponent for pages |
28
+ | [component-state.md](component-state.md) | createSignal, createMemo, createEffect, createDerivedArray |
29
+ | [component-refs.md](component-refs.md) | Refs, collection refs, element types |
30
+ | [component-data.md](component-data.md) | Immutable data, JSON Patch, patching |
31
+ | [render-results.md](render-results.md) | phaseOutput, RenderPipeline, errors, redirects |
32
+ | [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
33
+ | [cli-commands.md](cli-commands.md) | CLI commands: setup, validate, dev, agent-kit |
34
+ | `../references/<plugin>/` | Plugin reference data |
@@ -0,0 +1,228 @@
1
+ # CLI Commands Reference
2
+
3
+ ## jay-stack setup
4
+
5
+ Run plugin setup. Plugins can create configuration files, generate reference data, and validate their prerequisites.
6
+
7
+ ```bash
8
+ # Run setup for all installed plugins
9
+ jay-stack setup
10
+
11
+ # Run setup for a specific plugin
12
+ jay-stack setup wix-stores
13
+
14
+ # Re-run setup (e.g., after config change)
15
+ jay-stack setup wix-data --force
16
+ ```
17
+
18
+ Plugins declare their setup handler in `plugin.yaml`. Setup does two things:
19
+
20
+ 1. **Config templates**: Creates `config/<plugin>.yaml` with placeholder credentials if missing
21
+ 2. **Credential validation**: Attempts to initialize services, reports success or failure
22
+
23
+ Reference data (product catalogs, collection schemas) is generated by `jay-stack agent-kit`, not by setup.
24
+
25
+ Run this after installing new plugins, before `jay-stack agent-kit`.
26
+
27
+ ## jay-stack agent-kit
28
+
29
+ Materialize contracts, generate discovery indexes, and produce plugin reference data. Run this after setup.
30
+
31
+ ```bash
32
+ # Default: writes to agent-kit/
33
+ jay-stack agent-kit
34
+
35
+ # Custom output directory for contracts
36
+ jay-stack agent-kit --output my-output/
37
+
38
+ # List contracts without writing files
39
+ jay-stack agent-kit --list
40
+
41
+ # Filter to specific plugin
42
+ jay-stack agent-kit --plugin wix-stores
43
+
44
+ # Force re-materialization
45
+ jay-stack agent-kit --force
46
+
47
+ # Skip reference data generation
48
+ jay-stack agent-kit --no-references
49
+ ```
50
+
51
+ Outputs:
52
+
53
+ - `plugins-index.yaml`
54
+ - `materialized-contracts/<plugin>/*.jay-contract` (dynamic contracts)
55
+ - `references/<plugin>/` — plugin reference data (product catalogs, collection schemas, etc.)
56
+ - Documentation files (INSTRUCTIONS.md and reference docs)
57
+
58
+ ## jay-stack validate
59
+
60
+ Validate all `.jay-html` and `.jay-contract` files.
61
+
62
+ ```bash
63
+ # Validate entire project
64
+ jay-stack validate
65
+
66
+ # Validate a specific path
67
+ jay-stack validate src/pages/products/
68
+
69
+ # Verbose (per-file status)
70
+ jay-stack validate -v
71
+
72
+ # JSON output
73
+ jay-stack validate --json
74
+ ```
75
+
76
+ Example output:
77
+
78
+ ```
79
+ ✅ Jay Stack validation successful!
80
+ Scanned 5 .jay-html files, 3 .jay-contract files
81
+ No errors found.
82
+ ```
83
+
84
+ On failure:
85
+
86
+ ```
87
+ ❌ Jay Stack validation failed
88
+
89
+ Errors:
90
+ ❌ src/pages/products/page.jay-html
91
+ Unknown ref "nonExistentRef" - not found in contract
92
+
93
+ 1 error(s) found, 7 file(s) valid.
94
+ ```
95
+
96
+ Always run validate after creating or editing jay-html and contract files.
97
+
98
+ ## jay-stack params
99
+
100
+ Discover load param values for SSG route generation.
101
+
102
+ ```bash
103
+ # Discover slug values for product pages
104
+ jay-stack params wix-stores/product-page
105
+
106
+ # YAML output
107
+ jay-stack params wix-stores/product-page --yaml
108
+
109
+ # Verbose
110
+ jay-stack params wix-stores/product-page -v
111
+ ```
112
+
113
+ Format: `<plugin-name>/<contract-name>`
114
+
115
+ Example output:
116
+
117
+ ```json
118
+ [
119
+ { "slug": "ceramic-flower-vase" },
120
+ { "slug": "blue-running-shoes" },
121
+ { "slug": "organic-cotton-tshirt" }
122
+ ]
123
+
124
+ ✅ Found 3 param combination(s)
125
+ ```
126
+
127
+ Use this to discover what param values exist for dynamic routes like `[slug]`. Only works on contracts whose component has `loadParams`.
128
+
129
+ ## jay-stack action
130
+
131
+ Run a plugin action from the CLI. Use to discover data for populating pages.
132
+
133
+ ```bash
134
+ # Run with default input
135
+ jay-stack action wix-stores/searchProducts
136
+
137
+ # Run with input
138
+ jay-stack action wix-stores/searchProducts --input '{"query": "shoes", "limit": 5}'
139
+
140
+ # YAML output
141
+ jay-stack action wix-stores/getCategories --yaml
142
+
143
+ # Verbose
144
+ jay-stack action wix-stores/getProductBySlug --input '{"slug": "blue-shirt"}' -v
145
+ ```
146
+
147
+ Format: `<plugin-name>/<action-name>`
148
+
149
+ Action names are listed in `plugins-index.yaml` under each plugin's `actions:` array. Each action entry includes a `description` and a `path` to the `.jay-action` file. Read the `.jay-action` file to see the full input/output schemas before calling an action.
150
+
151
+ Example output:
152
+
153
+ ```json
154
+ {
155
+ "items": [
156
+ { "_id": "prod-1", "name": "Blue Shirt", "slug": "blue-shirt", "price": 29.99 },
157
+ { "_id": "prod-2", "name": "Red Hat", "slug": "red-hat", "price": 19.99 }
158
+ ],
159
+ "totalCount": 2
160
+ }
161
+ ```
162
+
163
+ If not found, lists available actions:
164
+
165
+ ```
166
+ ❌ Action "badName" not found.
167
+ Available actions: searchProducts, getProductBySlug, getCategories
168
+ ```
169
+
170
+ ## jay-stack dev
171
+
172
+ Start the development server.
173
+
174
+ ```bash
175
+ # Normal dev mode
176
+ jay-stack dev
177
+
178
+ # Test mode (enables health/shutdown endpoints)
179
+ jay-stack dev --test-mode
180
+
181
+ # Auto-timeout (implies test mode)
182
+ jay-stack dev --timeout 60
183
+ ```
184
+
185
+ ### Test mode endpoints
186
+
187
+ | Endpoint | Method | Response |
188
+ | ---------------- | ------ | --------------------------------------------------------------- |
189
+ | `/_jay/health` | GET | `{"status":"ready","port":3300,"editorPort":3301,"uptime":5.2}` |
190
+ | `/_jay/shutdown` | POST | `{"status":"shutting_down"}` |
191
+
192
+ ### Wait for server ready
193
+
194
+ Poll the health endpoint:
195
+
196
+ ```bash
197
+ # Bash
198
+ for i in {1..30}; do
199
+ curl -s http://localhost:3300/_jay/health | grep -q "ready" && break
200
+ sleep 1
201
+ done
202
+ ```
203
+
204
+ ```typescript
205
+ // TypeScript
206
+ async function waitForServer(timeout = 30000): Promise<string> {
207
+ const start = Date.now();
208
+ while (Date.now() - start < timeout) {
209
+ try {
210
+ const res = await fetch('http://localhost:3300/_jay/health');
211
+ if (res.ok) {
212
+ const { port } = await res.json();
213
+ return `http://localhost:${port}`;
214
+ }
215
+ } catch {
216
+ /* not ready */
217
+ }
218
+ await new Promise((r) => setTimeout(r, 500));
219
+ }
220
+ throw new Error('Server not ready');
221
+ }
222
+ ```
223
+
224
+ ### Shutdown
225
+
226
+ ```bash
227
+ curl -X POST http://localhost:3300/_jay/shutdown
228
+ ```
@@ -0,0 +1,109 @@
1
+ # Immutable Data and Patching
2
+
3
+ In Jay, ViewState data is immutable. Never mutate objects directly — use signals and JSON Patch for updates.
4
+
5
+ ## Immutable Data Model
6
+
7
+ ViewState objects passed to the render function are immutable snapshots. The framework compares old and new snapshots to determine what changed in the DOM.
8
+
9
+ ```typescript
10
+ // WRONG — never mutate directly
11
+ viewState.items.push(newItem);
12
+ viewState.count = 5;
13
+
14
+ // RIGHT — return new values from signals
15
+ const [count, setCount] = createSignal(0);
16
+ setCount(5);
17
+
18
+ return { render: () => ({ count: count() }) };
19
+ ```
20
+
21
+ ## JSON Patch for Complex Updates
22
+
23
+ For objects with many fields, use `createPatchableSignal` with JSON Patch operations instead of replacing the entire object:
24
+
25
+ ```typescript
26
+ import { createPatchableSignal } from '@jay-framework/component';
27
+ import { REPLACE, ADD, REMOVE } from '@jay-framework/json-patch';
28
+
29
+ const [state, setState, patchState] = createPatchableSignal({
30
+ title: 'Product',
31
+ price: 29.99,
32
+ tags: ['sale', 'featured'],
33
+ details: { color: 'red', size: 'M' },
34
+ });
35
+ ```
36
+
37
+ ### Patch Operations
38
+
39
+ **REPLACE** — Update an existing value:
40
+
41
+ ```typescript
42
+ patchState({ op: REPLACE, path: ['price'], value: 19.99 });
43
+ patchState({ op: REPLACE, path: ['details', 'color'], value: 'blue' });
44
+ ```
45
+
46
+ **ADD** — Add a new field or array item:
47
+
48
+ ```typescript
49
+ patchState({ op: ADD, path: ['tags', 1], value: 'new-tag' }); // Insert at index 1
50
+ patchState({ op: ADD, path: ['details', 'weight'], value: '500g' });
51
+ ```
52
+
53
+ **REMOVE** — Remove a field or array item:
54
+
55
+ ```typescript
56
+ patchState({ op: REMOVE, path: ['tags', 0] }); // Remove first tag
57
+ patchState({ op: REMOVE, path: ['details', 'size'] });
58
+ ```
59
+
60
+ **MOVE** — Move a value from one path to another:
61
+
62
+ ```typescript
63
+ import { MOVE } from '@jay-framework/json-patch';
64
+
65
+ patchState({ op: MOVE, from: ['tags', 0], path: ['tags', 2] }); // Reorder array item
66
+ patchState({ op: MOVE, from: ['details', 'color'], path: ['primaryColor'] }); // Relocate field
67
+ ```
68
+
69
+ ### Multiple Patches
70
+
71
+ Apply multiple patches at once — the framework batches them into a single update:
72
+
73
+ ```typescript
74
+ patchState(
75
+ { op: REPLACE, path: ['price'], value: 19.99 },
76
+ { op: REPLACE, path: ['details', 'color'], value: 'blue' },
77
+ );
78
+ ```
79
+
80
+ ### When to Use Patch vs Set
81
+
82
+ - **Simple values** (number, string, boolean): use `setSignal(newValue)`
83
+ - **Objects with few fields**: use `setSignal({ ...old, field: newValue })`
84
+ - **Complex nested objects**: use `patchState` for surgical updates
85
+ - **Arrays with identity tracking**: use `patchState` with ADD/REMOVE
86
+
87
+ ## createDerivedArray (Map Hook)
88
+
89
+ Transform an array reactively with smart caching. Only remaps items that actually changed:
90
+
91
+ ```typescript
92
+ import { createDerivedArray } from '@jay-framework/component';
93
+
94
+ const displayProducts = createDerivedArray(
95
+ () => products(),
96
+ (item, index, length) => ({
97
+ label: `${item().name} - ${formatPrice(item().price)}`,
98
+ position: `${index() + 1} of ${length()}`,
99
+ }),
100
+ );
101
+ ```
102
+
103
+ Key behavior:
104
+
105
+ - If an item's object identity hasn't changed, the cached mapped result is reused
106
+ - `index()` and `length()` are tracked — if you don't call them, changes to index/length won't trigger a remap
107
+ - Returns a `Getter<U[]>` — read with `displayProducts()`
108
+
109
+ See [component-state.md](component-state.md) for the full hook reference.
@@ -0,0 +1,117 @@
1
+ # Component Refs
2
+
3
+ Refs provide access to DOM elements declared as `interactive` in the contract. They are the second parameter of the interactive constructor.
4
+
5
+ ## Single Refs
6
+
7
+ A ref maps to one DOM element:
8
+
9
+ ```yaml
10
+ # Contract
11
+ - tag: addToCart
12
+ type: interactive
13
+ elementType: HTMLButtonElement
14
+ ```
15
+
16
+ ```typescript
17
+ // Component
18
+ .withInteractive(function MyComp(props, refs) {
19
+ refs.addToCart.onClick(() => {
20
+ // handle click
21
+ });
22
+ })
23
+ ```
24
+
25
+ ### Ref Methods
26
+
27
+ Refs provide type-safe access to the DOM element:
28
+
29
+ ```typescript
30
+ refs.submitButton.onClick(() => {
31
+ /* ... */
32
+ });
33
+
34
+ // exec$ gives direct access to the element and current ViewState
35
+ refs.submitButton.exec$((element, viewState) => {
36
+ element.disabled = viewState.isSubmitting;
37
+ });
38
+ ```
39
+
40
+ ## Collection Refs
41
+
42
+ When an interactive tag is inside a `repeated` sub-contract, the ref becomes a collection. In jay-html, collection refs use the `$` suffix:
43
+
44
+ ```html
45
+ <div forEach="items" trackBy="id">
46
+ <button ref="itemButton$">Click</button>
47
+ </div>
48
+ ```
49
+
50
+ The `$` is stripped from the name in the contract and component code:
51
+
52
+ ```yaml
53
+ # Contract
54
+ - tag: items
55
+ type: sub-contract
56
+ repeated: true
57
+ trackBy: id
58
+ tags:
59
+ - tag: id
60
+ type: data
61
+ dataType: string
62
+ - tag: itemButton
63
+ type: interactive
64
+ elementType: HTMLButtonElement
65
+ ```
66
+
67
+ ### Collection Ref Methods
68
+
69
+ ```typescript
70
+ // Map over all items in the collection
71
+ const labels = refs.itemButton.map((proxy, viewState, coordinate) => {
72
+ return viewState.name;
73
+ });
74
+
75
+ // Find a specific item
76
+ const target = refs.itemButton.find((viewState) => viewState.id === 'target-id');
77
+
78
+ // Find by coordinate
79
+ const target = refs.itemButton.find((viewState, coordinate) =>
80
+ sameCoordinate(coordinate, ['item-2', 'itemButton']),
81
+ );
82
+ ```
83
+
84
+ ## Element Types
85
+
86
+ Common element types for interactive tags:
87
+
88
+ | Element Type | Use For |
89
+ | --------------------- | --------------------- |
90
+ | `HTMLButtonElement` | Buttons, clickable |
91
+ | `HTMLAnchorElement` | Links |
92
+ | `HTMLInputElement` | Text inputs, checkbox |
93
+ | `HTMLSelectElement` | Dropdowns |
94
+ | `HTMLTextAreaElement` | Multi-line text |
95
+ | `HTMLFormElement` | Forms |
96
+ | `HTMLDivElement` | Generic containers |
97
+
98
+ Multiple element types (when the same ref may bind to different elements):
99
+
100
+ ```yaml
101
+ - tag: trigger
102
+ type: interactive
103
+ elementType: HTMLButtonElement | HTMLAnchorElement
104
+ ```
105
+
106
+ ## Data + Interactive
107
+
108
+ A tag can be both data and interactive:
109
+
110
+ ```yaml
111
+ - tag: quantityInput
112
+ type: [data, interactive]
113
+ dataType: number
114
+ elementType: HTMLInputElement
115
+ ```
116
+
117
+ This generates both a ViewState field and a ref.