@jay-framework/jay-stack-cli 0.19.6 → 0.19.7

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,37 @@
1
+ # Jay Stack Agent Kit
2
+
3
+ This agent kit provides role-specific guides for building a Jay Stack application. Each role covers a different aspect of the project.
4
+
5
+ ## Roles
6
+
7
+ ### Designer (`designer/`)
8
+
9
+ Creates `.jay-html` pages and templates. Binds to contract data using template syntax — data bindings, conditions, loops, and refs. Styles pages with CSS.
10
+
11
+ **Use when:** building or modifying the visual UI of pages and components.
12
+
13
+ ### Developer (`developer/`)
14
+
15
+ Creates page components (`page.ts`) using `makeJayStackComponent`, defines page contracts (`page.jay-contract`), configures routing, and sets up project services. Also creates headfull components for shared UI sections (headers, footers).
16
+
17
+ **Use when:** adding page logic, defining data shapes, configuring the project, or creating shared components.
18
+
19
+ ### Plugin (`plugin/`)
20
+
21
+ Creates reusable headless components packaged as plugins. Defines contracts, actions, services, and CLI commands that other projects consume.
22
+
23
+ **Use when:** building a reusable plugin that will be installed by other projects.
24
+
25
+ ### DevOps (`devops/`)
26
+
27
+ Handles production builds, deployment configuration, serving modes, and cache invalidation.
28
+
29
+ **Use when:** deploying the application or configuring production infrastructure.
30
+
31
+ ## Shared Guides
32
+
33
+ ### Contracts (`contracts/`)
34
+
35
+ Contract authoring guide shared across all roles. Covers syntax, page contracts, component contracts, linked contracts, and graduated examples.
36
+
37
+ **Start here:** [contracts/GUIDE.md](contracts/GUIDE.md) — decision tree for what kind of contract to write.
@@ -0,0 +1,70 @@
1
+ # Contract Authoring Guide
2
+
3
+ Contracts (`.jay-contract` files) define the data shape, interaction points, and rendering phases for a component. They are the source of truth shared between the component implementation and the template.
4
+
5
+ ## What Kind of Contract Do I Need?
6
+
7
+ ### Page contract
8
+
9
+ A page has its own data that isn't fully provided by plugins.
10
+
11
+ - Place as `page.jay-contract` next to `page.jay-html` and `page.ts`
12
+ - Use `params` for dynamic route segments (`slug`, `category`)
13
+ - Can coexist with plugin headless contracts on the same page
14
+ - See [page-contracts.md](page-contracts.md)
15
+
16
+ ### Headfull component contract
17
+
18
+ A shared UI section (header, footer, sidebar) that appears across multiple pages.
19
+
20
+ - Place in `src/components/<name>/` alongside the `.ts` and `.jay-html` files
21
+ - Use `props` for configuration passed by the parent page
22
+ - No `params` (components don't own routes)
23
+ - See [component-contracts.md](component-contracts.md)
24
+
25
+ ### Plugin contract
26
+
27
+ A headless component provided by a plugin for others to consume.
28
+
29
+ - Place in the plugin's `lib/contracts/` directory
30
+ - Declared in `plugin.yaml`
31
+ - Can be static (file) or dynamic (generated at build time)
32
+ - See the plugin role guide for plugin-specific concerns
33
+
34
+ ### Shared sub-contract
35
+
36
+ A reusable piece extracted from a larger contract and linked via `link:` syntax.
37
+
38
+ - Place alongside the contracts that reference it
39
+ - No `props` or `params` (data flows from the parent)
40
+ - See [linked-contracts.md](linked-contracts.md)
41
+
42
+ ## Decision Checklist
43
+
44
+ | Question | If yes |
45
+ | --------------------------------------------------- | ---------------------------------------------------------------------------------------- |
46
+ | Does the component own a URL route? | Add `params` — see [page-contracts.md](page-contracts.md) |
47
+ | Is it configured by a parent? | Add `props` — see [component-contracts.md](component-contracts.md) |
48
+ | Does it have nested repeated data? | Use `sub-contract` with `repeated: true` and `trackBy` |
49
+ | Is a sub-contract reused across multiple contracts? | Extract to a separate file, use `link:` — see [linked-contracts.md](linked-contracts.md) |
50
+ | Does data change per request? | Use `fast` or `fast+interactive` phase |
51
+ | Does data update on the client after interaction? | Use `fast+interactive` phase |
52
+ | Is the data static / known at build time? | Use `slow` phase |
53
+ | Does the user click, type, or select something? | Add `interactive` tag with the right `elementType` |
54
+
55
+ ## Contract Syntax Reference
56
+
57
+ See [syntax.md](syntax.md) for the full YAML format: tag types, phases, data types, props, params, async data, and validation rules.
58
+
59
+ ## Examples
60
+
61
+ Start with the simplest example that matches your use case, then add complexity as needed:
62
+
63
+ | Example | Complexity | Key patterns |
64
+ | ------------------------------------------------------ | -------------- | ----------------------------------------------------------- |
65
+ | [mini-cart](examples/mini-cart.md) | Trivial | Variant + interactive refs |
66
+ | [cart-indicator](examples/cart-indicator.md) | Simple | Flat data, variants, phase choices |
67
+ | [category-list](examples/category-list.md) | Medium | Props, repeated sub-contracts |
68
+ | [product-card](examples/product-card.md) | Medium-complex | Linked sub-contracts, variants, multiple ref types |
69
+ | [product-page](examples/product-page.md) | Complex | Params, nested repeated sub-contracts, linked sub-contracts |
70
+ | [composing-contracts](examples/composing-contracts.md) | Pattern | How contracts link together into a hierarchy |
@@ -0,0 +1,82 @@
1
+ # Headfull Component Contracts
2
+
3
+ Headfull components in Jay Stack are full-stack and must have a contract. The contract defines the data the component provides to its template.
4
+
5
+ ## When to Use
6
+
7
+ Use a headfull component contract for shared UI sections that appear across multiple pages: headers, footers, sidebars, navigation menus.
8
+
9
+ ## File Structure
10
+
11
+ Each headfull component lives in its own subdirectory under `src/components/`:
12
+
13
+ ```
14
+ src/components/site-header/
15
+ site-header.ts # makeJayStackComponent logic
16
+ site-header.jay-html # template
17
+ site-header.jay-contract # contract (required)
18
+ ```
19
+
20
+ ## Contract with Props
21
+
22
+ Headfull components use `props` (not `params`) for configuration passed by the parent page:
23
+
24
+ ```yaml
25
+ name: site-header
26
+ description: Site-wide header with navigation and cart indicator.
27
+ props:
28
+ - name: logoUrl
29
+ type: string
30
+ description: URL for the site logo
31
+ - name: showSearch
32
+ type: boolean
33
+ default: 'true'
34
+ description: Whether to show the search bar
35
+ tags:
36
+ - tag: siteName
37
+ type: data
38
+ dataType: string
39
+ phase: slow
40
+ - tag: navLinks
41
+ type: sub-contract
42
+ repeated: true
43
+ trackBy: _id
44
+ tags:
45
+ - tag: _id
46
+ type: data
47
+ dataType: string
48
+ - tag: label
49
+ type: data
50
+ dataType: string
51
+ - tag: navLink
52
+ type: interactive
53
+ elementType: HTMLAnchorElement
54
+ ```
55
+
56
+ ## Importing in jay-html
57
+
58
+ The import must include the `contract` attribute:
59
+
60
+ ```html
61
+ <script
62
+ type="application/jay-headfull"
63
+ src="../components/site-header/site-header"
64
+ names="SiteHeader"
65
+ contract="../components/site-header/site-header.jay-contract"
66
+ ></script>
67
+ ```
68
+
69
+ Usage in the page body:
70
+
71
+ ```html
72
+ <jay:SiteHeader logoUrl="/logo.png" />
73
+ ```
74
+
75
+ ## Component Contract vs Page Contract
76
+
77
+ | | Page contract | Component contract |
78
+ | ------------ | -------------------------------------- | -------------------------------------------------------------------- |
79
+ | **Location** | `src/pages/.../page.jay-contract` | `src/components/<name>/<name>.jay-contract` |
80
+ | **Params** | Yes (from route segments) | No (components don't own routes) |
81
+ | **Props** | Rarely | Yes (configured by parent) |
82
+ | **Import** | `<script type="application/jay-data">` | `<script type="application/jay-headfull">` with `contract` attribute |
@@ -0,0 +1,60 @@
1
+ # Example: Cart Indicator
2
+
3
+ A simple flat contract for a header widget. Shows phase choices and the difference between data, variant, and interactive tags.
4
+
5
+ ```yaml
6
+ name: cart-indicator
7
+ description: Cart indicator showing item count, typically used in site header.
8
+
9
+ tags:
10
+ - tag: itemCount
11
+ type: data
12
+ dataType: number
13
+ phase: fast+interactive
14
+ description: Total number of items in cart
15
+
16
+ - tag: hasItems
17
+ type: variant
18
+ dataType: boolean
19
+ phase: fast+interactive
20
+ description: Whether cart has any items
21
+
22
+ - tag: cartButton
23
+ type: interactive
24
+ elementType: HTMLButtonElement
25
+ description: Button to open cart
26
+
27
+ - tag: cartLink
28
+ type: interactive
29
+ elementType: HTMLAnchorElement
30
+ description: Link to cart page
31
+
32
+ - tag: isLoading
33
+ type: variant
34
+ dataType: boolean
35
+ phase: fast+interactive
36
+ description: Whether cart data is loading
37
+
38
+ - tag: justAdded
39
+ type: variant
40
+ dataType: boolean
41
+ phase: fast+interactive
42
+ description: Briefly true after an item is added (for animation)
43
+ ```
44
+
45
+ ## Why these choices
46
+
47
+ - **Everything is `fast+interactive`** — cart data is per-session (not build-time), and updates live when items are added.
48
+ - **`hasItems` is a variant, not computed from `itemCount`** — the template can use `if="hasItems"` directly without comparing numbers.
49
+ - **`justAdded` is a transient variant** — the component briefly sets it to true for CSS animation, then resets it.
50
+ - **Both `cartButton` and `cartLink`** — gives the designer flexibility to use either a button (opens mini-cart) or a link (navigates to cart page).
51
+
52
+ ## Jay-HTML usage
53
+
54
+ ```html
55
+ <div class="cart-indicator">
56
+ <a ref="cartLink" class="cart-icon">
57
+ <span if="hasItems" class="badge">{itemCount}</span>
58
+ </a>
59
+ </div>
60
+ ```
@@ -0,0 +1,77 @@
1
+ # Example: Category List
2
+
3
+ A medium-complexity contract with props and a repeated sub-contract. Shows how to configure a component at design time.
4
+
5
+ ```yaml
6
+ name: category-list
7
+ description: Displays store categories. Use for category navigation grids and menus.
8
+ props:
9
+ - name: parentCategory
10
+ type: string
11
+ description: Parent category slug. Only direct children of this category are shown.
12
+ tags:
13
+ - tag: categories
14
+ type: sub-contract
15
+ repeated: true
16
+ trackBy: _id
17
+ description: List of visible store categories
18
+ tags:
19
+ - tag: _id
20
+ type: data
21
+ dataType: string
22
+
23
+ - tag: name
24
+ type: data
25
+ dataType: string
26
+ required: true
27
+
28
+ - tag: slug
29
+ type: data
30
+ dataType: string
31
+
32
+ - tag: description
33
+ type: data
34
+ dataType: string
35
+
36
+ - tag: productCount
37
+ type: data
38
+ dataType: number
39
+
40
+ - tag: imageUrl
41
+ type: data
42
+ dataType: string
43
+ meta: { mediaType: wix-image }
44
+
45
+ - tag: categoryLink
46
+ type: interactive
47
+ elementType: HTMLAnchorElement
48
+
49
+ - tag: hasCategories
50
+ type: variant
51
+ dataType: boolean
52
+ description: Whether there are any categories
53
+ ```
54
+
55
+ ## Why these choices
56
+
57
+ - **`props` instead of `params`** — this is a widget placed on a page, not a page itself. The parent decides which categories to show.
58
+ - **No explicit phases** — category data is static and available at all phases.
59
+ - **`trackBy: _id`** — each category has a stable GUID for efficient list diffing.
60
+ - **`hasCategories` variant** — lets the template show an empty state without checking array length.
61
+ - **`meta: { mediaType: wix-image }`** — tells validators this string is a Wix media URL.
62
+
63
+ ## Jay-HTML usage
64
+
65
+ ```html
66
+ <jay:category-list parentCategory="shop">
67
+ <div if="hasCategories" class="category-grid">
68
+ <div forEach="categories" trackBy="_id">
69
+ <a ref="categoryLink" class="category-card">
70
+ <img src="{imageUrl}" alt="{name}" />
71
+ <span>{name}</span>
72
+ <span class="count">({productCount})</span>
73
+ </a>
74
+ </div>
75
+ </div>
76
+ </jay:category-list>
77
+ ```
@@ -0,0 +1,100 @@
1
+ # Example: Composing Contracts
2
+
3
+ How contracts link together into a reusable hierarchy. This example shows the wix-stores composition pattern.
4
+
5
+ ## The Hierarchy
6
+
7
+ ```
8
+ media.jay-contract ← Trivial: url + mediaType
9
+
10
+ media-gallery.jay-contract ← Links to media, adds thumbnail navigation
11
+
12
+ product-page.jay-contract ← Links to media-gallery, adds options, pricing
13
+
14
+ product-options.jay-contract ← Reusable option/choice structure
15
+
16
+ product-card.jay-contract ← Links to product-options for quick-add
17
+
18
+ product-search.jay-contract ← Links to product-card for search results
19
+ ```
20
+
21
+ ## Level 1: Leaf contract
22
+
23
+ The simplest building block. No sub-contracts, no links.
24
+
25
+ ```yaml
26
+ # media.jay-contract
27
+ name: media
28
+ description: Single media item.
29
+ tags:
30
+ - { tag: url, type: data, dataType: string, meta: { mediaType: wix-image } }
31
+ - { tag: mediaType, type: variant, dataType: 'enum (IMAGE | VIDEO)' }
32
+ ```
33
+
34
+ ## Level 2: Composing leaves
35
+
36
+ Links to a leaf contract and adds its own structure.
37
+
38
+ ```yaml
39
+ # media-gallery.jay-contract
40
+ name: mediaGallery
41
+ description: Image/video gallery with thumbnail navigation.
42
+ tags:
43
+ - tag: selectedMedia
44
+ type: sub-contract
45
+ link: ./media
46
+
47
+ - tag: availableMedia
48
+ type: sub-contract
49
+ repeated: true
50
+ trackBy: mediaId
51
+ tags:
52
+ - { tag: mediaId, type: data }
53
+ - { tag: media, type: sub-contract, link: ./media }
54
+ - tag: selected
55
+ type: [variant, interactive]
56
+ dataType: enum(selected | notSelected)
57
+ elementType: HTMLImageElement | HTMLDivElement
58
+ ```
59
+
60
+ ## Level 3: Page contract composing everything
61
+
62
+ The product page links to the gallery (which links to media) and adds its own options, pricing, and actions.
63
+
64
+ ```yaml
65
+ # product-page.jay-contract (abbreviated)
66
+ name: product-page
67
+ params:
68
+ slug: string
69
+ tags:
70
+ - { tag: productName, type: data, dataType: string }
71
+ - { tag: mediaGallery, type: sub-contract, phase: fast+interactive, link: ./media-gallery }
72
+ - { tag: price, type: data, dataType: string, phase: fast+interactive }
73
+ # ... options, actions, etc.
74
+ ```
75
+
76
+ ## Parallel composition
77
+
78
+ The same `product-options.jay-contract` is linked by both the product page (inline options) and the product card (quick-add):
79
+
80
+ ```yaml
81
+ # In product-card.jay-contract
82
+ - { tag: quickOption, type: sub-contract, link: ./product-options }
83
+ # In product-page.jay-contract — options are defined inline because they have
84
+ # page-specific additions (text choice dropdown, modifier support)
85
+ ```
86
+
87
+ ## When to extract vs inline
88
+
89
+ | Extract into a linked file | Keep inline |
90
+ | ---------------------------------------------------------- | --------------------------------------- |
91
+ | Used by 2+ contracts | Used by only one contract |
92
+ | Complex enough to be its own concept (media, product-card) | Simple nested object (pricing, summary) |
93
+ | Has its own identity that designers/developers reference | Just a grouping of related fields |
94
+
95
+ ## Benefits of composition
96
+
97
+ - **Single source of truth** — change `media.jay-contract` once, all galleries update
98
+ - **Readability** — each file stays focused on one concept
99
+ - **Reusability** — `product-card` is used in search, related products, and category pages
100
+ - **Independent testing** — each contract level can be validated independently
@@ -0,0 +1,43 @@
1
+ # Example: Mini Cart
2
+
3
+ A trivial contract with one variant and two interactive refs. Good starting point for understanding the basics.
4
+
5
+ ```yaml
6
+ name: mini-cart
7
+ description: Drawer that auto-opens when a product is added to cart.
8
+
9
+ tags:
10
+ - tag: isOpen
11
+ type: variant
12
+ dataType: boolean
13
+ phase: fast+interactive
14
+ description: Whether the mini-cart drawer is currently open
15
+
16
+ - tag: openButton
17
+ type: interactive
18
+ elementType: HTMLButtonElement
19
+ description: Button to manually open the mini-cart drawer
20
+
21
+ - tag: closeButton
22
+ type: interactive
23
+ elementType: HTMLButtonElement
24
+ description: Button to close the mini-cart drawer
25
+ ```
26
+
27
+ ## Why these choices
28
+
29
+ - **`isOpen` is `fast+interactive`** — the drawer state starts on the server (closed) and toggles on the client.
30
+ - **Interactive tags have no explicit phase** — they're always `fast+interactive`.
31
+ - **No props or params** — the mini-cart doesn't need configuration and doesn't own a route.
32
+
33
+ ## Jay-HTML usage
34
+
35
+ ```html
36
+ <div class="mini-cart-overlay" if="isOpen">
37
+ <div class="mini-cart-drawer">
38
+ <button ref="closeButton">Close</button>
39
+ <!-- cart content here -->
40
+ </div>
41
+ </div>
42
+ <button ref="openButton">Cart</button>
43
+ ```
@@ -0,0 +1,70 @@
1
+ # Example: Product Card
2
+
3
+ A medium-complex contract designed as a reusable sub-contract. Shows linked sub-contracts, variants for conditional rendering, and multiple interactive element types.
4
+
5
+ ```yaml
6
+ name: product-card
7
+ description: Single product card with image, price, and quick-add. Used in product grids and search results.
8
+ tags:
9
+ - { tag: _id, type: data, dataType: string }
10
+ - { tag: name, type: data, dataType: string, required: true }
11
+ - { tag: slug, type: data, dataType: string }
12
+ - { tag: productUrl, type: data, dataType: string }
13
+ - { tag: productLink, type: interactive, elementType: HTMLAnchorElement }
14
+
15
+ # Media — inline sub-contract for the card thumbnail
16
+ - tag: thumbnail
17
+ type: sub-contract
18
+ tags:
19
+ - { tag: url, type: data, dataType: string, meta: { mediaType: wix-image } }
20
+ - { tag: altText, type: data, dataType: string }
21
+ - { tag: width, type: data, dataType: number }
22
+ - { tag: height, type: data, dataType: number }
23
+
24
+ # Pricing — fast+interactive because it changes with variant selection
25
+ - { tag: price, type: data, dataType: string, phase: fast+interactive }
26
+ - { tag: strikethroughPrice, type: data, dataType: string, phase: fast+interactive }
27
+ - { tag: hasDiscount, type: variant, dataType: boolean }
28
+
29
+ # Inventory — inline sub-contract with enum variant
30
+ - tag: inventory
31
+ type: sub-contract
32
+ tags:
33
+ - tag: availabilityStatus
34
+ type: variant
35
+ dataType: enum (IN_STOCK | OUT_OF_STOCK | PARTIALLY_OUT_OF_STOCK)
36
+
37
+ # Quick-add — linked sub-contracts for option selection
38
+ - tag: quickAddType
39
+ type: variant
40
+ dataType: enum (SIMPLE | SINGLE_OPTION | COLOR_AND_TEXT_OPTIONS | NEEDS_CONFIGURATION)
41
+
42
+ - { tag: quickOption, type: sub-contract, link: ./product-options }
43
+ - { tag: secondQuickOption, type: sub-contract, link: ./product-options }
44
+
45
+ - { tag: addToCartButton, type: interactive, elementType: HTMLButtonElement }
46
+ - { tag: isAddingToCart, type: variant, dataType: boolean, phase: fast+interactive }
47
+ - { tag: cardContainer, type: interactive, elementType: HTMLElement }
48
+ ```
49
+
50
+ ## Why these choices
51
+
52
+ - **No props or params** — this is a sub-contract, not a standalone component. Data flows from the parent (search results, related products).
53
+ - **`link: ./product-options`** — the option/choice structure is shared with the product page, so it's extracted into its own file.
54
+ - **`quickAddType` enum** — drives which quick-add UI to show. The designer uses `if="quickAddType===SIMPLE"` etc.
55
+ - **`cardContainer` is `HTMLElement`** — not a button or link, just a generic element for mouseenter preloading.
56
+ - **Inline `thumbnail` sub-contract** — only used here, so no need to extract.
57
+
58
+ ## Jay-HTML usage
59
+
60
+ ```html
61
+ <a ref="productLink" class="product-card" ref="cardContainer">
62
+ <img src="{thumbnail.url}" alt="{thumbnail.altText}" />
63
+ <h3>{name}</h3>
64
+ <div class="price">
65
+ <span if="hasDiscount" class="original">{strikethroughPrice}</span>
66
+ <span>{price}</span>
67
+ </div>
68
+ <button if="quickAddType===SIMPLE" ref="addToCartButton">Add to Cart</button>
69
+ </a>
70
+ ```
@@ -0,0 +1,125 @@
1
+ # Example: Product Page
2
+
3
+ A complex page contract with params, nested repeated sub-contracts, linked sub-contracts, and multiple rendering phases.
4
+
5
+ ```yaml
6
+ name: product-page
7
+ description: Full product detail page with variants, options, media gallery, and add-to-cart.
8
+ params:
9
+ slug: string
10
+ prefix: string?
11
+ category: string?
12
+ tags:
13
+ - { tag: _id, type: data, dataType: string }
14
+ - { tag: productName, type: data, dataType: string, required: true }
15
+ - { tag: description, type: data, dataType: html-string }
16
+ - { tag: brand, type: data, dataType: string }
17
+ - { tag: ribbon, type: data, dataType: string }
18
+ - { tag: productType, type: variant, dataType: 'enum (PHYSICAL | DIGITAL)' }
19
+
20
+ # Linked sub-contract for the media gallery
21
+ - tag: mediaGallery
22
+ type: sub-contract
23
+ phase: fast+interactive
24
+ link: ./media-gallery
25
+
26
+ # Pricing — per-variant, changes on client
27
+ - { tag: sku, type: data, dataType: string, phase: fast+interactive }
28
+ - { tag: price, type: data, dataType: string, phase: fast+interactive }
29
+ - { tag: strikethroughPrice, type: data, dataType: string, phase: fast+interactive }
30
+ - {
31
+ tag: stockStatus,
32
+ type: variant,
33
+ dataType: 'enum (OUT_OF_STOCK | IN_STOCK)',
34
+ phase: fast+interactive,
35
+ }
36
+
37
+ # Quantity controls — dual-type tag (data + interactive)
38
+ - tag: quantity
39
+ type: sub-contract
40
+ tags:
41
+ - { tag: decrementButton, type: interactive, elementType: HTMLButtonElement }
42
+ - { tag: incrementButton, type: interactive, elementType: HTMLButtonElement }
43
+ - {
44
+ tag: quantity,
45
+ type: [data, interactive],
46
+ dataType: number,
47
+ elementType: HTMLInputElement,
48
+ }
49
+
50
+ # Call to action
51
+ - { tag: addToCartButton, type: interactive, elementType: HTMLButtonElement, required: true }
52
+ - { tag: buyNowButton, type: interactive, elementType: HTMLButtonElement, required: true }
53
+ - { tag: actionsEnabled, type: variant, dataType: boolean, phase: fast+interactive }
54
+
55
+ # Options — nested repeated sub-contracts (options → choices)
56
+ - tag: options
57
+ type: sub-contract
58
+ repeated: true
59
+ trackBy: _id
60
+ tags:
61
+ - { tag: _id, type: data, dataType: string }
62
+ - { tag: name, type: data, dataType: string }
63
+ - {
64
+ tag: optionRenderType,
65
+ type: variant,
66
+ dataType: 'enum (TEXT_CHOICES | COLOR_SWATCH_CHOICES)',
67
+ }
68
+ - { tag: textChoice, type: interactive, elementType: HTMLSelectElement }
69
+
70
+ - tag: choices
71
+ type: sub-contract
72
+ repeated: true
73
+ trackBy: choiceId
74
+ tags:
75
+ - { tag: choiceId, type: data, dataType: string }
76
+ - { tag: name, type: data, dataType: string }
77
+ - { tag: colorCode, type: data, dataType: string }
78
+ - { tag: inStock, type: data, dataType: boolean }
79
+ - { tag: isSelected, type: variant, dataType: boolean, phase: fast+interactive }
80
+ - { tag: choiceButton, type: interactive, elementType: HTMLButtonElement }
81
+
82
+ # Info sections — repeated content blocks
83
+ - tag: infoSections
84
+ type: sub-contract
85
+ repeated: true
86
+ trackBy: _id
87
+ tags:
88
+ - { tag: _id, type: data, dataType: string }
89
+ - { tag: title, type: data, dataType: string }
90
+ - { tag: plainDescription, type: data, dataType: html-string }
91
+ ```
92
+
93
+ ## Why these choices
94
+
95
+ - **`params`** — this is a page contract with dynamic route segments (`/products/[slug]`).
96
+ - **`link: ./media-gallery`** — the gallery is complex enough to warrant its own file and is reusable.
97
+ - **Options are inline** — despite being complex, they're specific to the product page and not reused elsewhere.
98
+ - **`html-string` for description** — the product description contains HTML formatting from the CMS.
99
+ - **`[data, interactive]` for quantity** — the input both displays the current value and accepts user input.
100
+ - **No phase on product name** — available at all phases (SSG, SSR, and client).
101
+ - **`fast+interactive` on pricing** — prices change when a variant is selected on the client.
102
+ - **Nested `repeated`** — options contain choices, both with their own `trackBy` for efficient diffing.
103
+
104
+ ## Jay-HTML usage
105
+
106
+ ```html
107
+ <h1>{productName}</h1>
108
+ <div class="price">
109
+ <span>{price}</span>
110
+ <span if="stockStatus===OUT_OF_STOCK" class="out-of-stock">Out of Stock</span>
111
+ </div>
112
+
113
+ <div forEach="options" trackBy="_id">
114
+ <h3>{name}</h3>
115
+ <div if="optionRenderType===COLOR_SWATCH_CHOICES">
116
+ <div forEach="choices" trackBy="choiceId">
117
+ <button ref="choiceButton" style="background: {colorCode}" class="swatch" if="isSelected">
118
+ selected
119
+ </button>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ <button ref="addToCartButton">Add to Cart</button>
125
+ ```