@jay-framework/jay-stack-cli 0.15.5 → 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.
- package/agent-kit-template/{INSTRUCTIONS.md → designer/INSTRUCTIONS.md} +11 -8
- package/agent-kit-template/{jay-html-syntax.md → designer/jay-html-components.md} +89 -158
- package/agent-kit-template/designer/jay-html-styling.md +97 -0
- package/agent-kit-template/designer/jay-html-syntax.md +44 -0
- package/agent-kit-template/designer/jay-html-template-syntax.md +203 -0
- package/agent-kit-template/developer/INSTRUCTIONS.md +34 -0
- package/agent-kit-template/developer/cli-commands.md +228 -0
- package/agent-kit-template/developer/component-data.md +109 -0
- package/agent-kit-template/developer/component-refs.md +117 -0
- package/agent-kit-template/developer/component-state.md +140 -0
- package/agent-kit-template/developer/configuration.md +76 -0
- package/agent-kit-template/developer/page-components.md +103 -0
- package/agent-kit-template/developer/page-contracts.md +114 -0
- package/agent-kit-template/developer/project-structure.md +242 -0
- package/agent-kit-template/developer/render-results.md +112 -0
- package/agent-kit-template/developer/routing.md +161 -0
- package/agent-kit-template/developer/seo-guide.md +93 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +40 -0
- package/agent-kit-template/plugin/actions-guide.md +125 -0
- package/agent-kit-template/plugin/component-context.md +103 -0
- package/agent-kit-template/plugin/component-data.md +109 -0
- package/agent-kit-template/plugin/component-refs.md +117 -0
- package/agent-kit-template/plugin/component-state.md +140 -0
- package/agent-kit-template/plugin/component-structure.md +174 -0
- package/agent-kit-template/plugin/contracts-guide.md +193 -0
- package/agent-kit-template/plugin/plugin-structure.md +194 -0
- package/agent-kit-template/plugin/render-results.md +112 -0
- package/agent-kit-template/plugin/seo-guide.md +93 -0
- package/agent-kit-template/plugin/services-guide.md +116 -0
- package/agent-kit-template/plugin/validation.md +101 -0
- package/dist/index.js +678 -60
- package/package.json +10 -10
- /package/agent-kit-template/{cli-commands.md → designer/cli-commands.md} +0 -0
- /package/agent-kit-template/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +0 -0
- /package/agent-kit-template/{project-structure.md → designer/project-structure.md} +0 -0
- /package/agent-kit-template/{routing.md → designer/routing.md} +0 -0
|
@@ -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.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Component State Hooks
|
|
2
|
+
|
|
3
|
+
All hooks are used inside the interactive phase (the `withInteractive` constructor function). They provide reactive state management for client-side behavior.
|
|
4
|
+
|
|
5
|
+
## createSignal
|
|
6
|
+
|
|
7
|
+
Creates a reactive getter/setter pair:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { createSignal } from '@jay-framework/component';
|
|
11
|
+
|
|
12
|
+
const [count, setCount] = createSignal(0);
|
|
13
|
+
|
|
14
|
+
// Read
|
|
15
|
+
count(); // 0
|
|
16
|
+
|
|
17
|
+
// Write
|
|
18
|
+
setCount(5); // set to 5
|
|
19
|
+
setCount((n) => n + 1); // increment
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Can initialize from a getter (reactive dependency):
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
const [label, setLabel] = createSignal(() => 'Hello ' + props.name());
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## createPatchableSignal
|
|
29
|
+
|
|
30
|
+
Creates a signal with JSON Patch support for fine-grained updates to complex objects:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { createPatchableSignal } from '@jay-framework/component';
|
|
34
|
+
import { REPLACE } from '@jay-framework/json-patch';
|
|
35
|
+
|
|
36
|
+
const [data, setData, patchData] = createPatchableSignal({
|
|
37
|
+
label: 'Hello',
|
|
38
|
+
count: 0,
|
|
39
|
+
nested: { value: 42 },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Patch a specific field
|
|
43
|
+
patchData({ op: REPLACE, path: ['label'], value: 'Updated' });
|
|
44
|
+
|
|
45
|
+
// Patch nested field
|
|
46
|
+
patchData({ op: REPLACE, path: ['nested', 'value'], value: 99 });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
See [component-data.md](component-data.md) for more on immutable data and patching.
|
|
50
|
+
|
|
51
|
+
## createMemo
|
|
52
|
+
|
|
53
|
+
Creates a memoized computed value that recalculates only when dependencies change:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { createMemo } from '@jay-framework/component';
|
|
57
|
+
|
|
58
|
+
const fullName = createMemo(() => `${firstName()} ${lastName()}`);
|
|
59
|
+
|
|
60
|
+
// Read
|
|
61
|
+
fullName(); // recomputes only when firstName() or lastName() change
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With initial value:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const total = createMemo((prev) => prev + latestValue(), 0);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## createEffect
|
|
71
|
+
|
|
72
|
+
Registers a side effect that runs on mount and when dependencies change. Optional cleanup function:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { createEffect } from '@jay-framework/component';
|
|
76
|
+
|
|
77
|
+
createEffect(() => {
|
|
78
|
+
const handler = () => setWindowWidth(window.innerWidth);
|
|
79
|
+
window.addEventListener('resize', handler);
|
|
80
|
+
return () => window.removeEventListener('resize', handler); // cleanup
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Effects track reactive dependencies automatically:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
createEffect(() => {
|
|
88
|
+
document.title = `${count()} items`; // reruns when count() changes
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## createDerivedArray
|
|
93
|
+
|
|
94
|
+
Efficiently maps an array with smart caching. Only remaps items that actually changed:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { createDerivedArray } from '@jay-framework/component';
|
|
98
|
+
|
|
99
|
+
const displayItems = createDerivedArray(
|
|
100
|
+
() => products(),
|
|
101
|
+
(item, index, length) => ({
|
|
102
|
+
name: item().name,
|
|
103
|
+
displayPrice: formatPrice(item().price),
|
|
104
|
+
isLast: index() === length() - 1,
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Read the mapped array
|
|
109
|
+
displayItems();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Key optimizations:
|
|
113
|
+
|
|
114
|
+
- Reuses mapped items when the source item hasn't changed
|
|
115
|
+
- Only tracks `index()` and `length()` if you actually call them
|
|
116
|
+
- Uses object identity (not deep equality) for cache hits
|
|
117
|
+
|
|
118
|
+
## createEvent
|
|
119
|
+
|
|
120
|
+
Creates an event emitter for component-to-parent communication:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { createEvent } from '@jay-framework/component';
|
|
124
|
+
|
|
125
|
+
const onChange = createEvent<{ value: number }>((emitter) => {
|
|
126
|
+
emitter.emit({ value: count() });
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## useReactive
|
|
131
|
+
|
|
132
|
+
Gets the current reactive context for advanced use cases:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { useReactive } from '@jay-framework/component';
|
|
136
|
+
|
|
137
|
+
const reactive = useReactive();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Most components won't need this — prefer the higher-level hooks above.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Component Structure
|
|
2
|
+
|
|
3
|
+
Full-stack components use `makeJayStackComponent` with a fluent builder API and three rendering phases.
|
|
4
|
+
|
|
5
|
+
## Basic Structure
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { makeJayStackComponent, phaseOutput } from '@jay-framework/fullstack-component';
|
|
9
|
+
import type { MyContract } from './my-contract.generated';
|
|
10
|
+
|
|
11
|
+
export const myComponent = makeJayStackComponent<MyContract>()
|
|
12
|
+
.withSlowlyRender(async (props) => {
|
|
13
|
+
const data = await fetchStaticData();
|
|
14
|
+
return phaseOutput(
|
|
15
|
+
{ title: data.title, description: data.description }, // ViewState
|
|
16
|
+
{ productId: data.id }, // CarryForward
|
|
17
|
+
);
|
|
18
|
+
})
|
|
19
|
+
.withFastRender(async (props) => {
|
|
20
|
+
return phaseOutput({ price: await getPrice(), inStock: true }, {});
|
|
21
|
+
})
|
|
22
|
+
.withInteractive(function MyComponent(props, refs) {
|
|
23
|
+
// Client-side hooks here
|
|
24
|
+
return { render: () => ({}) };
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Builder API
|
|
29
|
+
|
|
30
|
+
### `.withProps<T>()`
|
|
31
|
+
|
|
32
|
+
Declare the props type (must match contract `props`):
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
makeJayStackComponent<MyContract>().withProps<{ productId: string; currency?: string }>();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `.withServices(...markers)`
|
|
39
|
+
|
|
40
|
+
Inject server-side services:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
.withServices(DATABASE_SERVICE, CACHE_SERVICE)
|
|
44
|
+
.withSlowlyRender(async (props, db, cache) => {
|
|
45
|
+
// db and cache are injected
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `.withContexts(...markers)`
|
|
50
|
+
|
|
51
|
+
Consume client-side contexts in the interactive phase:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
.withContexts(CART_CONTEXT)
|
|
55
|
+
.withInteractive(function MyComp(props, refs, cartCtx) {
|
|
56
|
+
// cartCtx available in interactive phase
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `.withLoadParams(fn)`
|
|
61
|
+
|
|
62
|
+
Generate URL params for SSG (static site generation). Returns an async iterable of param arrays:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
.withLoadParams(async function* (db) {
|
|
66
|
+
const products = await db.getAllProducts();
|
|
67
|
+
yield products.map(p => ({ slug: p.slug }));
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `.withSlowlyRender(fn)` — Build-time rendering
|
|
72
|
+
|
|
73
|
+
Runs during SSG. Has access to props and services. Returns `phaseOutput(viewState, carryForward)`.
|
|
74
|
+
|
|
75
|
+
CarryForward data is passed to the fast phase but not included in the ViewState.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
.withSlowlyRender(async (props, db) => {
|
|
79
|
+
const product = await db.getProduct(props.slug);
|
|
80
|
+
if (!product) return notFound('Product not found');
|
|
81
|
+
return phaseOutput(
|
|
82
|
+
{ title: product.name, description: product.desc },
|
|
83
|
+
{ productId: product.id }, // Available in fast phase
|
|
84
|
+
);
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `.withFastRender(fn)` — Request-time rendering
|
|
89
|
+
|
|
90
|
+
Runs on each request. Receives props (including `query` for query parameters) and carry-forward from slow phase.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
.withFastRender(async (props, db) => {
|
|
94
|
+
const price = await db.getPrice(props.carryForward.productId);
|
|
95
|
+
return phaseOutput({ price, inStock: price > 0 }, {});
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `.withClientDefaults(fn)` — Default client ViewState
|
|
100
|
+
|
|
101
|
+
Provides default values for client-side ViewState before hydration:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
.withClientDefaults(() => ({
|
|
105
|
+
quantity: 1,
|
|
106
|
+
selectedVariant: 'default',
|
|
107
|
+
}))
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `.withInteractive(ComponentConstructor)` — Client-side logic
|
|
111
|
+
|
|
112
|
+
The interactive phase runs in the browser. Use hooks here (see component-state.md):
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
.withInteractive(function ProductPage(props, refs) {
|
|
116
|
+
const [quantity, setQuantity] = createSignal(1);
|
|
117
|
+
|
|
118
|
+
refs.addToCart.onClick(() => {
|
|
119
|
+
addToCartAction({ productId: props.productId, quantity: quantity() });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
render: () => ({
|
|
124
|
+
quantity: quantity(),
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Render Return Types
|
|
131
|
+
|
|
132
|
+
Each phase can return:
|
|
133
|
+
|
|
134
|
+
- `phaseOutput(viewState, carryForward)` — success
|
|
135
|
+
- `notFound()`, `badRequest()`, `unauthorized()`, `forbidden()` — client errors
|
|
136
|
+
- `serverError5xx(status, message)` — server errors
|
|
137
|
+
- `redirect3xx(status, location)` — redirects
|
|
138
|
+
|
|
139
|
+
See [render-results.md](render-results.md) for details.
|
|
140
|
+
|
|
141
|
+
## Props and Contract Alignment
|
|
142
|
+
|
|
143
|
+
The component's props type must match the contract's `props` section:
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
# Contract
|
|
147
|
+
props:
|
|
148
|
+
- name: productId
|
|
149
|
+
type: string
|
|
150
|
+
required: true
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Component
|
|
155
|
+
makeJayStackComponent<MyContract>().withProps<{ productId: string }>();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Params and Contract Alignment
|
|
159
|
+
|
|
160
|
+
If the contract has `params`, the component should use `withLoadParams` and the route must have matching dynamic segments:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
# Contract
|
|
164
|
+
params:
|
|
165
|
+
slug: string
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Component
|
|
170
|
+
.withLoadParams(async function* (db) {
|
|
171
|
+
const items = await db.getAll();
|
|
172
|
+
yield items.map(i => ({ slug: i.slug }));
|
|
173
|
+
})
|
|
174
|
+
```
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Contract Authoring Guide
|
|
2
|
+
|
|
3
|
+
Contracts (`.jay-contract` files) are the source of truth for a component's data shape. Define the contract before implementing the component.
|
|
4
|
+
|
|
5
|
+
## Basic Structure
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
name: ProductCard
|
|
9
|
+
description: Displays a single product with price and add-to-cart. Use for product grids and featured sections.
|
|
10
|
+
props:
|
|
11
|
+
- name: productId
|
|
12
|
+
type: string
|
|
13
|
+
required: true
|
|
14
|
+
description: The product to display
|
|
15
|
+
params:
|
|
16
|
+
slug: string
|
|
17
|
+
tags:
|
|
18
|
+
- tag: name
|
|
19
|
+
type: data
|
|
20
|
+
dataType: string
|
|
21
|
+
phase: slow
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Tag Types
|
|
25
|
+
|
|
26
|
+
### `data` — Read-only values
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
- tag: productName
|
|
30
|
+
type: data
|
|
31
|
+
dataType: string # string (default), number, boolean, date
|
|
32
|
+
required: true # optional, defaults to false
|
|
33
|
+
phase: slow # slow, fast, or fast+interactive
|
|
34
|
+
description: Display name
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `variant` — Enum/boolean for conditionals
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
- tag: status
|
|
41
|
+
type: variant
|
|
42
|
+
dataType: enum (AVAILABLE | OUT_OF_STOCK | PREORDER)
|
|
43
|
+
phase: fast+interactive
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `interactive` — Element refs for user interaction
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
- tag: addToCart
|
|
50
|
+
type: interactive
|
|
51
|
+
elementType: HTMLButtonElement # HTMLAnchorElement, HTMLInputElement, HTMLSelectElement, etc.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Interactive tags are always `fast+interactive` — do not specify a phase.
|
|
55
|
+
|
|
56
|
+
A tag can be both data and interactive:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
- tag: quantityInput
|
|
60
|
+
type: [data, interactive]
|
|
61
|
+
dataType: number
|
|
62
|
+
elementType: HTMLInputElement
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `sub-contract` — Nested objects
|
|
66
|
+
|
|
67
|
+
Inline:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
- tag: pricing
|
|
71
|
+
type: sub-contract
|
|
72
|
+
tags:
|
|
73
|
+
- tag: amount
|
|
74
|
+
type: data
|
|
75
|
+
dataType: number
|
|
76
|
+
- tag: currency
|
|
77
|
+
type: data
|
|
78
|
+
dataType: string
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Linked (reference another contract file):
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
- tag: author
|
|
85
|
+
type: sub-contract
|
|
86
|
+
link: ./author.jay-contract
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `sub-contract` with `repeated: true` — Arrays
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
- tag: items
|
|
93
|
+
type: sub-contract
|
|
94
|
+
repeated: true
|
|
95
|
+
trackBy: id # Required: identifies each item
|
|
96
|
+
phase: fast
|
|
97
|
+
tags:
|
|
98
|
+
- tag: id
|
|
99
|
+
type: data
|
|
100
|
+
dataType: string
|
|
101
|
+
- tag: name
|
|
102
|
+
type: data
|
|
103
|
+
dataType: string
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`trackBy` must reference a `data` tag with `string` or `number` type within the same sub-contract.
|
|
107
|
+
|
|
108
|
+
## Async Data
|
|
109
|
+
|
|
110
|
+
Wrap any tag in `Promise<T>` with `async: true`:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
- tag: reviews
|
|
114
|
+
type: data
|
|
115
|
+
async: true
|
|
116
|
+
dataType: string # Compiles to Promise<string>
|
|
117
|
+
|
|
118
|
+
- tag: relatedProducts
|
|
119
|
+
type: sub-contract
|
|
120
|
+
repeated: true
|
|
121
|
+
trackBy: id
|
|
122
|
+
async: true # Compiles to Promise<Array<...>>
|
|
123
|
+
tags:
|
|
124
|
+
- tag: id
|
|
125
|
+
type: data
|
|
126
|
+
dataType: string
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Rendering Phases
|
|
130
|
+
|
|
131
|
+
Each tag has a phase that determines when its data is available:
|
|
132
|
+
|
|
133
|
+
| Phase | When | Use For |
|
|
134
|
+
| ------------------ | ------------------ | --------------------------------------- |
|
|
135
|
+
| `slow` | Build time (SSG) | Static content, SEO data, product names |
|
|
136
|
+
| `fast` | Request time (SSR) | Per-request data, live pricing, stock |
|
|
137
|
+
| `fast+interactive` | Request + client | Data that also updates on the client |
|
|
138
|
+
|
|
139
|
+
**How to choose:**
|
|
140
|
+
|
|
141
|
+
- Can the data be known at build time? Use `slow`
|
|
142
|
+
- Does it change per request (user, time, session)? Use `fast`
|
|
143
|
+
- Does it also update on the client after interaction? Use `fast+interactive`
|
|
144
|
+
- Interactive tags (refs) are always `fast+interactive`
|
|
145
|
+
|
|
146
|
+
**Phase rules for arrays:** Child phases must be >= parent phase. If the array is `fast`, all children must be `fast` or later.
|
|
147
|
+
|
|
148
|
+
## Props vs Params
|
|
149
|
+
|
|
150
|
+
### Props — Component configuration
|
|
151
|
+
|
|
152
|
+
Props are passed by the parent component or jay-html template. Use for component inputs like IDs, configuration flags, display options.
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
props:
|
|
156
|
+
- name: productId
|
|
157
|
+
type: string
|
|
158
|
+
required: true
|
|
159
|
+
description: The product to display
|
|
160
|
+
- name: showPricing
|
|
161
|
+
type: boolean
|
|
162
|
+
default: 'true'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Params — URL route segments
|
|
166
|
+
|
|
167
|
+
Params come from dynamic route segments (`[slug]`, `[[lang]]`, `[...path]`). Use for page-level routing data.
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
params:
|
|
171
|
+
slug: string # required — from [slug]
|
|
172
|
+
lang: string? # optional — from [[lang]]
|
|
173
|
+
path: string[] # catch-all — from [...path]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Description Field
|
|
177
|
+
|
|
178
|
+
Always include a `description` at the contract level explaining when to use this contract:
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
name: product-search
|
|
182
|
+
description: Product listing with filters, sorting, and pagination. Use for search results and category pages.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Validation Rules
|
|
186
|
+
|
|
187
|
+
- Tag names must be unique at each level
|
|
188
|
+
- `repeated: true` requires `trackBy`
|
|
189
|
+
- `trackBy` must reference a `data` tag with `string` or `number` type
|
|
190
|
+
- Interactive tags cannot have an explicit `phase`
|
|
191
|
+
- Sub-contracts must have either `tags` (inline) or `link` (external), not both
|
|
192
|
+
- Array children must have phase >= parent phase
|
|
193
|
+
- Prop names must be unique
|