@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,112 @@
1
+ # Render Results
2
+
3
+ Each rendering phase (slow, fast) returns a render result indicating success, error, or redirect.
4
+
5
+ ## phaseOutput — Success
6
+
7
+ Returns ViewState data and optional carry-forward data for the next phase:
8
+
9
+ ```typescript
10
+ import { phaseOutput } from '@jay-framework/fullstack-component';
11
+
12
+ return phaseOutput(
13
+ { title: 'My Product', price: 29.99 }, // ViewState — sent to template
14
+ { productId: 'abc123' }, // CarryForward — passed to next phase only
15
+ );
16
+ ```
17
+
18
+ CarryForward is available in the next phase via `props.carryForward` but is not part of the ViewState.
19
+
20
+ ## Error Results
21
+
22
+ Return errors to stop rendering and show an error page:
23
+
24
+ ```typescript
25
+ import {
26
+ notFound,
27
+ badRequest,
28
+ unauthorized,
29
+ forbidden,
30
+ serverError5xx,
31
+ clientError4xx,
32
+ } from '@jay-framework/fullstack-component';
33
+
34
+ // 404
35
+ if (!product) return notFound('Product not found');
36
+
37
+ // 400
38
+ if (!input.query) return badRequest('Query is required');
39
+
40
+ // 401
41
+ if (!session) return unauthorized('Please log in');
42
+
43
+ // 403
44
+ if (!canAccess) return forbidden('Access denied');
45
+
46
+ // Custom 4xx
47
+ return clientError4xx(429, 'Rate limit exceeded');
48
+
49
+ // 5xx
50
+ return serverError5xx(500, 'Database connection failed');
51
+ ```
52
+
53
+ ## Redirects
54
+
55
+ ```typescript
56
+ import { redirect3xx } from '@jay-framework/fullstack-component';
57
+
58
+ return redirect3xx(301, '/new-location');
59
+ return redirect3xx(302, `/products/${product.slug}`);
60
+ ```
61
+
62
+ ## RenderPipeline — Composable Rendering
63
+
64
+ For complex render logic, `RenderPipeline` chains operations with automatic error propagation:
65
+
66
+ ```typescript
67
+ import { RenderPipeline } from '@jay-framework/fullstack-component';
68
+
69
+ const Pipeline = RenderPipeline.for<SlowViewState, CarryForward>();
70
+
71
+ return Pipeline.try(() => db.getProduct(props.slug))
72
+ .map((product) => product ?? Pipeline.notFound('Product not found'))
73
+ .map(async (product) => ({
74
+ ...product,
75
+ reviews: await db.getReviews(product.id),
76
+ }))
77
+ .toPhaseOutput((product) => ({
78
+ viewState: { title: product.name, price: product.price },
79
+ carryForward: { productId: product.id },
80
+ }));
81
+ ```
82
+
83
+ ### Pipeline Factory Methods
84
+
85
+ ```typescript
86
+ const P = RenderPipeline.for<VS, CF>();
87
+
88
+ P.ok(value); // Wrap a value
89
+ P.try(() => fetchData()); // Wrap a function (catches errors)
90
+ P.from(previousPhaseResult); // Continue from a prior phase result
91
+ P.notFound('message'); // Error pipeline
92
+ P.badRequest('message'); // Error pipeline
93
+ P.unauthorized('message'); // Error pipeline
94
+ P.forbidden('message'); // Error pipeline
95
+ P.serverError(500, 'message'); // Error pipeline
96
+ P.redirect(301, '/path'); // Redirect pipeline
97
+ ```
98
+
99
+ ### Pipeline Chain Methods
100
+
101
+ ```typescript
102
+ pipeline
103
+ .map(value => transform(value)) // Transform the value
104
+ .map(async value => await asyncOp(value)) // Async transform
105
+ .recover(error => P.ok(fallbackValue)) // Recover from errors
106
+ .toPhaseOutput(value => ({ // Convert to PhaseOutput
107
+ viewState: { ... },
108
+ carryForward: { ... },
109
+ }));
110
+ ```
111
+
112
+ Errors short-circuit — if any step returns an error pipeline, subsequent `.map()` calls are skipped.
@@ -0,0 +1,161 @@
1
+ # Directory-Based Routing
2
+
3
+ ## Route Structure
4
+
5
+ Pages live under `src/pages/`. Directory names become URL segments.
6
+
7
+ ```
8
+ src/pages/
9
+ ├── page.jay-html → /
10
+ ├── about/
11
+ │ └── page.jay-html → /about
12
+ ├── products/
13
+ │ ├── page.jay-html → /products
14
+ │ └── [slug]/
15
+ │ └── page.jay-html → /products/:slug
16
+ ├── blog/
17
+ │ ├── page.jay-html → /blog
18
+ │ └── [[slug]]/
19
+ │ └── page.jay-html → /blog/:slug (optional)
20
+ └── files/
21
+ └── [...path]/
22
+ └── page.jay-html → /files/* (catch-all)
23
+ ```
24
+
25
+ ## Dynamic Routes
26
+
27
+ | Syntax | Meaning | Example |
28
+ | ------------ | ------------------ | --------------------------------------- |
29
+ | `[param]` | Required parameter | `[slug]` → `/products/:slug` |
30
+ | `[[param]]` | Optional parameter | `[[slug]]` → `/blog` or `/blog/my-post` |
31
+ | `[...param]` | Catch-all | `[...path]` → matches any sub-path |
32
+
33
+ ## Route Priority
34
+
35
+ Static routes match before dynamic routes (most specific first):
36
+
37
+ 1. **Static segments** (exact match) — highest priority
38
+ 2. **`[param]`** — required dynamic param
39
+ 3. **`[[param]]`** — optional param
40
+ 4. **`[...param]`** — catch-all — lowest priority
41
+
42
+ ## Static Route Overrides
43
+
44
+ A static route can override a dynamic route for a specific URL — giving one particular page a custom layout while the dynamic route handles everything else:
45
+
46
+ ```
47
+ src/pages/products/
48
+ ├── [slug]/page.jay-html # dynamic: /products/:slug
49
+ └── ceramic-flower-vase/page.jay-html # static override for this specific product
50
+ ```
51
+
52
+ The static `ceramic-flower-vase/` route takes priority over `[slug]/` for that URL, but all other product URLs still use the dynamic route.
53
+
54
+ ### Static Override Params (`jay-params`)
55
+
56
+ Static override routes often use the same contract as the dynamic route they override. Since the static route has no dynamic directory segment, the params must be declared explicitly using `<script type="application/jay-params">`:
57
+
58
+ ```html
59
+ <!-- src/pages/products/ceramic-flower-vase/page.jay-html -->
60
+ <html>
61
+ <head>
62
+ <script type="application/jay-params">
63
+ slug: ceramic-flower-vase
64
+ </script>
65
+ <script
66
+ type="application/jay-headless"
67
+ plugin="wix-stores"
68
+ contract="product-page"
69
+ key="product"
70
+ ></script>
71
+ </head>
72
+ <body>
73
+ <h1>{product.productName}</h1>
74
+ </body>
75
+ </html>
76
+ ```
77
+
78
+ The script body is YAML. The declared params are passed to the component as if extracted from a dynamic URL segment. Without this, the component would receive no param values.
79
+
80
+ ## Page Files
81
+
82
+ Each page directory can contain:
83
+
84
+ | File | Purpose |
85
+ | ------------------- | ----------------------------------- |
86
+ | `page.jay-html` | Template (required for rendering) |
87
+ | `page.jay-contract` | Page-level data contract (optional) |
88
+
89
+ ### page.jay-contract
90
+
91
+ Defines the page's own ViewState — data that the page's server-side code provides:
92
+
93
+ ```yaml
94
+ name: Page
95
+ tags:
96
+ - tag: title
97
+ type: data
98
+ dataType: string
99
+ phase: slow
100
+ - tag: items
101
+ type: sub-contract
102
+ repeated: true
103
+ trackBy: id
104
+ tags:
105
+ - tag: id
106
+ type: data
107
+ dataType: string
108
+ - tag: name
109
+ type: data
110
+ dataType: string
111
+ ```
112
+
113
+ ## Dynamic Routes and Contract Params
114
+
115
+ When a component on the page — whether the page contract, a headless component, or a headfull full-stack component — declares `params`, the page should be placed in a dynamic route directory that provides those params.
116
+
117
+ For example, if a headless component's contract declares:
118
+
119
+ ```yaml
120
+ name: product-page
121
+ params:
122
+ slug: string
123
+ tags:
124
+ - ...
125
+ ```
126
+
127
+ Then the page using this component should live at a route that provides a `slug` param:
128
+
129
+ ```
130
+ src/pages/products/[slug]/page.jay-html
131
+ ```
132
+
133
+ Multiple components on the same page can each declare params. The route directory must provide all required params across all components. For example, if the page contract requires `lang` and a headless component requires `slug`, the page should live at `src/pages/[lang]/products/[slug]/page.jay-html`.
134
+
135
+ ### Discovering Param Values
136
+
137
+ For SSG with dynamic routes, the plugin component provides a `loadParams` generator that yields all valid param combinations. Use it to discover what routes will be generated:
138
+
139
+ ```bash
140
+ jay-stack params wix-stores/product-page
141
+ # Output: [{"slug": "blue-shirt"}, {"slug": "red-hat"}, ...]
142
+ ```
143
+
144
+ Params are always strings (URL params).
145
+
146
+ ## Query Parameters
147
+
148
+ URL query parameters (`?page=2&sort=price`) are available in the **fast render phase only** via `props.query`:
149
+
150
+ ```typescript
151
+ .withFastRender(async (props, carryForward, dbService) => {
152
+ const page = parseInt(props.query.page || '1');
153
+ const sort = props.query.sort || 'name';
154
+ const products = await dbService.getProducts({ page, sort });
155
+ return phaseOutput({ products, currentPage: page }, {});
156
+ })
157
+ ```
158
+
159
+ - `props.query` is `Record<string, string>` — empty `{}` when no query string
160
+ - Not available in the slow phase (compile error) — slow results are cached by path params only
161
+ - In the interactive phase, use `new URLSearchParams(window.location.search)` directly
@@ -0,0 +1,93 @@
1
+ # SEO Head Tags
2
+
3
+ Components inject `<title>`, `<meta>`, `<link>` tags into the HTML `<head>` during SSR by returning `headTags` from `phaseOutput()`.
4
+
5
+ ## Basic Usage
6
+
7
+ ```typescript
8
+ return phaseOutput(
9
+ { title: product.name, price: product.price },
10
+ { productId: product.id },
11
+ {
12
+ headTags: [
13
+ { tag: 'title', children: `${product.name} | My Store` },
14
+ { tag: 'meta', attrs: { name: 'description', content: product.description } },
15
+ { tag: 'meta', attrs: { property: 'og:title', content: product.name } },
16
+ { tag: 'meta', attrs: { property: 'og:description', content: product.description } },
17
+ { tag: 'meta', attrs: { property: 'og:image', content: product.imageUrl } },
18
+ { tag: 'link', attrs: { rel: 'canonical', href: canonicalUrl } },
19
+ ],
20
+ },
21
+ );
22
+ ```
23
+
24
+ ## HeadTag Type
25
+
26
+ ```typescript
27
+ interface HeadTag {
28
+ tag: string; // 'title', 'meta', 'link', etc.
29
+ attrs?: Record<string, string>; // HTML attributes
30
+ children?: string; // Text content (for <title>, <script>, etc.)
31
+ }
32
+ ```
33
+
34
+ ## Common SEO Tags
35
+
36
+ ```typescript
37
+ // Page title
38
+ { tag: 'title', children: 'Product Name | Store' }
39
+
40
+ // Meta description
41
+ { tag: 'meta', attrs: { name: 'description', content: 'Product description here' } }
42
+
43
+ // Open Graph
44
+ { tag: 'meta', attrs: { property: 'og:title', content: 'Product Name' } }
45
+ { tag: 'meta', attrs: { property: 'og:description', content: 'Description' } }
46
+ { tag: 'meta', attrs: { property: 'og:image', content: 'https://...' } }
47
+ { tag: 'meta', attrs: { property: 'og:type', content: 'product' } }
48
+
49
+ // Twitter Card
50
+ { tag: 'meta', attrs: { name: 'twitter:card', content: 'summary_large_image' } }
51
+ { tag: 'meta', attrs: { name: 'twitter:title', content: 'Product Name' } }
52
+
53
+ // Canonical URL
54
+ { tag: 'link', attrs: { rel: 'canonical', href: 'https://example.com/products/slug' } }
55
+
56
+ // JSON-LD structured data
57
+ { tag: 'script', attrs: { type: 'application/ld+json' }, children: JSON.stringify(structuredData) }
58
+ ```
59
+
60
+ ## Mapping Generic SEO Data
61
+
62
+ If the data source provides a generic structure (array of tags with type/props/children), map it:
63
+
64
+ ```typescript
65
+ const headTags = seoData.tags.map((tag) => ({
66
+ tag: tag.type,
67
+ attrs: Object.fromEntries(tag.props.map((p) => [p.key, p.value])),
68
+ children: tag.children,
69
+ }));
70
+
71
+ return phaseOutput(viewState, carryForward, { headTags });
72
+ ```
73
+
74
+ ## Phase Rules
75
+
76
+ - Return headTags from **slow** phase for build-time SEO data (product name, description)
77
+ - Return headTags from **fast** phase for per-request data (pricing, availability)
78
+ - Fast phase headTags **replace** slow phase entirely (no merge)
79
+ - No interactive phase — head tags are SSR-only
80
+
81
+ ## Collision Rules
82
+
83
+ - `<title>` — singleton, last-write-wins
84
+ - `<meta name="X">` — keyed by `name`, last-write-wins
85
+ - `<meta property="X">` — keyed by `property`, last-write-wins
86
+ - `<link rel="canonical">` — singleton, last-write-wins
87
+ - Other tags — always included (no dedup)
88
+ - A warning is logged on collision between different components
89
+
90
+ ## Restrictions
91
+
92
+ - Head tags from components inside `forEach` are ignored
93
+ - The framework handles HTML escaping automatically
@@ -0,0 +1,40 @@
1
+ # Jay Plugin Development — Agent Kit
2
+
3
+ This folder contains guides for creating jay-stack plugins: contracts, headless components, server actions, and services.
4
+
5
+ ## What is a Jay Plugin?
6
+
7
+ A plugin provides headless components (data + interactions, no UI) that project designers use via contracts. Plugins can be standalone npm packages or inline within a project (see `examples/jay-stack/fake-shop`).
8
+
9
+ ## Workflow
10
+
11
+ 1. **Define contracts first** — the contract is the source of truth
12
+ 2. **Implement components** matching the contracts
13
+ 3. **Define actions** with `.jay-action` metadata
14
+ 4. **Set up `plugin.yaml`**
15
+ 5. **Validate** with `jay-stack validate-plugin`
16
+
17
+ ## Guides
18
+
19
+ | File | Topic |
20
+ | ------------------------------------------------ | ----------------------------------------------------------------------- |
21
+ | [contracts-guide.md](contracts-guide.md) | Contract format: tags, types, phases, props, params, sub-contracts |
22
+ | [plugin-structure.md](plugin-structure.md) | plugin.yaml, package layout, exports |
23
+ | [component-structure.md](component-structure.md) | makeJayStackComponent, builder API, three-phase rendering |
24
+ | [component-state.md](component-state.md) | createSignal, createMemo, createEffect, createDerivedArray, createEvent |
25
+ | [component-refs.md](component-refs.md) | Refs, collection refs, element types |
26
+ | [component-data.md](component-data.md) | Immutable data, JSON Patch, createPatchableSignal |
27
+ | [component-context.md](component-context.md) | Context hooks: provide, reactive, global |
28
+ | [render-results.md](render-results.md) | phaseOutput, RenderPipeline, errors, redirects |
29
+ | [actions-guide.md](actions-guide.md) | makeJayAction, makeJayQuery, .jay-action files |
30
+ | [services-guide.md](services-guide.md) | createJayService, makeJayInit |
31
+ | [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
32
+ | [validation.md](validation.md) | jay-stack validate-plugin usage |
33
+ | `../references/<plugin>/` | Plugin reference data |
34
+
35
+ ## Key Principles
36
+
37
+ - **Contract is the source of truth** — define it before implementing the component
38
+ - **Data is immutable** — never mutate ViewState directly, use JSON Patch
39
+ - **Phase-aware** — choose the right rendering phase for each piece of data
40
+ - **Props for configuration, params for URLs** — props are passed by parent components, params come from route segments
@@ -0,0 +1,125 @@
1
+ # Server Actions
2
+
3
+ Actions provide RPC-style server endpoints for client-to-server communication.
4
+
5
+ ## makeJayAction — Mutations (POST)
6
+
7
+ ```typescript
8
+ import { makeJayAction } from '@jay-framework/fullstack-component';
9
+
10
+ export const addToCart = makeJayAction('cart.addToCart')
11
+ .withServices(CART_SERVICE)
12
+ .withHandler(async (input: { productId: string; quantity: number }, cartService) => {
13
+ const cart = await cartService.addItem(input.productId, input.quantity);
14
+ return { cartItemCount: cart.items.length };
15
+ });
16
+ ```
17
+
18
+ ## makeJayQuery — Reads (GET)
19
+
20
+ Queries use GET and support caching:
21
+
22
+ ```typescript
23
+ import { makeJayQuery } from '@jay-framework/fullstack-component';
24
+
25
+ export const searchProducts = makeJayQuery('products.search')
26
+ .withServices(PRODUCTS_SERVICE)
27
+ .withCaching({ maxAge: 60 })
28
+ .withHandler(async (input: { query: string; page?: number }, productsDb) => {
29
+ const results = await productsDb.search(input.query, input.page);
30
+ return { products: results.items, totalCount: results.total };
31
+ });
32
+ ```
33
+
34
+ ## Builder API
35
+
36
+ ```typescript
37
+ makeJayAction('name')
38
+ .withServices(SERVICE1, SERVICE2) // Inject services
39
+ .withMethod('PUT') // Override HTTP method (default: POST for actions)
40
+ .withCaching({ maxAge: 60 }) // Enable caching (queries only)
41
+ .withHandler(async (input, svc1, svc2) => {
42
+ // Define handler
43
+ return result;
44
+ });
45
+ ```
46
+
47
+ ## ActionError
48
+
49
+ Throw typed errors from action handlers:
50
+
51
+ ```typescript
52
+ import { ActionError } from '@jay-framework/fullstack-component';
53
+
54
+ throw new ActionError('OUT_OF_STOCK', 'Only 2 units available');
55
+ throw new ActionError('INVALID_INPUT', 'Product ID is required');
56
+ ```
57
+
58
+ ## Calling Actions from Client
59
+
60
+ Actions are callable functions on the client:
61
+
62
+ ```typescript
63
+ .withInteractive(function MyComp(props, refs) {
64
+ refs.addToCart.onClick(async () => {
65
+ const result = await addToCart({
66
+ productId: props.productId,
67
+ quantity: 1,
68
+ });
69
+ // result.cartItemCount
70
+ });
71
+ })
72
+ ```
73
+
74
+ ## Calling Actions from Server
75
+
76
+ When called from server-side code (e.g., within a render phase), services are automatically injected — no network call is made.
77
+
78
+ ## .jay-action Metadata Files
79
+
80
+ Each action should have a `.jay-action` file describing its input/output schemas for agent discovery:
81
+
82
+ ```yaml
83
+ name: searchProducts
84
+ description: Search products with text, filters, sorting, and pagination
85
+
86
+ import:
87
+ productCard: product-card.jay-contract
88
+
89
+ inputSchema:
90
+ query: string
91
+ filters?:
92
+ inStockOnly?: boolean
93
+ minPrice?: number
94
+ maxPrice?: number
95
+ sortBy?: enum(relevance | price_asc | price_desc)
96
+ pageSize?: number
97
+
98
+ outputSchema:
99
+ products:
100
+ - productCard
101
+ totalCount: number
102
+ hasMore: boolean
103
+ ```
104
+
105
+ ### Jay-Type Notation for Schemas
106
+
107
+ | Notation | Meaning |
108
+ | ------------------------- | ------------------------- |
109
+ | `prop: string` | Required string |
110
+ | `prop?: number` | Optional number |
111
+ | `prop: boolean` | Required boolean |
112
+ | `prop: enum(a \| b \| c)` | Required enum |
113
+ | `prop:` + nested block | Nested object |
114
+ | `prop:` + `- child: type` | Array of objects |
115
+ | `prop: record(T)` | Record with typed values |
116
+ | `prop: importedName` | Type from `import:` block |
117
+
118
+ ## Type Helpers
119
+
120
+ ```typescript
121
+ import { ActionInput, ActionOutput, isJayAction } from '@jay-framework/fullstack-component';
122
+
123
+ type SearchInput = ActionInput<typeof searchProducts>;
124
+ type SearchOutput = ActionOutput<typeof searchProducts>;
125
+ ```
@@ -0,0 +1,103 @@
1
+ # Component Context
2
+
3
+ Context provides a way to share state between components without passing props through every level.
4
+
5
+ ## Context Markers
6
+
7
+ Create a typed marker to identify a context:
8
+
9
+ ```typescript
10
+ import { createContextMarker } from '@jay-framework/component';
11
+
12
+ interface CartContext {
13
+ itemCount: () => number;
14
+ addItem: (productId: string) => void;
15
+ }
16
+
17
+ const CART_CONTEXT = createContextMarker<CartContext>('CartContext');
18
+ ```
19
+
20
+ ## provideContext
21
+
22
+ Provide a non-reactive context value to child components:
23
+
24
+ ```typescript
25
+ import { provideContext } from '@jay-framework/component';
26
+
27
+ provideContext(CART_CONTEXT, {
28
+ itemCount: () => items().length,
29
+ addItem: (id) => addToCartAction({ productId: id }),
30
+ });
31
+ ```
32
+
33
+ ## provideReactiveContext
34
+
35
+ Provide a reactive context — the factory function has access to hooks:
36
+
37
+ ```typescript
38
+ import { provideReactiveContext, createSignal } from '@jay-framework/component';
39
+
40
+ const cartCtx = provideReactiveContext(CART_CONTEXT, () => {
41
+ const [items, setItems] = createSignal<CartItem[]>([]);
42
+ return {
43
+ itemCount: () => items().length,
44
+ addItem: (id) => setItems((prev) => [...prev, { productId: id }]),
45
+ };
46
+ });
47
+ ```
48
+
49
+ The returned value is the context instance, usable in the same component.
50
+
51
+ ## registerReactiveGlobalContext
52
+
53
+ Register a context globally during client initialization (in `makeJayInit`):
54
+
55
+ ```typescript
56
+ import { registerReactiveGlobalContext, createSignal } from '@jay-framework/component';
57
+
58
+ export const init = makeJayInit().withClient(() => {
59
+ registerReactiveGlobalContext(CART_CONTEXT, () => {
60
+ const [items, setItems] = createSignal<CartItem[]>([]);
61
+ return {
62
+ itemCount: () => items().length,
63
+ addItem: (id) => setItems((prev) => [...prev, { productId: id }]),
64
+ };
65
+ });
66
+ });
67
+ ```
68
+
69
+ Global contexts are available to all components without explicit providing.
70
+
71
+ ## Consuming Context
72
+
73
+ Components consume contexts via `.withContexts()` on the builder:
74
+
75
+ ```typescript
76
+ makeJayStackComponent<MyContract>()
77
+ .withContexts(CART_CONTEXT)
78
+ .withInteractive(function MyComp(props, refs, cartCtx) {
79
+ refs.addToCart.onClick(() => {
80
+ cartCtx.addItem(props.productId);
81
+ });
82
+
83
+ return {
84
+ render: () => ({
85
+ cartCount: cartCtx.itemCount(),
86
+ }),
87
+ };
88
+ });
89
+ ```
90
+
91
+ ## Listing in plugin.yaml
92
+
93
+ If your plugin provides contexts for other plugins to consume, list them in `plugin.yaml`:
94
+
95
+ ```yaml
96
+ contexts:
97
+ - name: cart
98
+ marker: CART_CONTEXT
99
+ description: Client-side cart state (item count, add/remove items, totals)
100
+ doc: ./docs/cart-context.md # optional — markdown documentation
101
+ ```
102
+
103
+ This makes the context discoverable in `plugins-index.yaml`. If `doc` is provided, the file must exist and be exported from the package.