@jay-framework/jay-stack-cli 0.22.2 → 0.23.0

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.
@@ -31,7 +31,7 @@ There is no standalone "interactive" phase. Any tag with `type: interactive` (re
31
31
  4. **Read actions** — read `.jay-action` files (paths from plugins-index) to see action descriptions, input schemas, and output schemas. This tells you what data each action accepts and returns.
32
32
  5. **Read references** — check `references/<plugin>/` for pre-generated discovery data (product catalogs, collection schemas, etc.). These are generated by `jay-stack agent-kit` and contain real data from the site.
33
33
  6. **Discover data** — run `jay-stack params <plugin>/<contract>` for SSG route params, `jay-stack action <plugin>/<action>` for data discovery. Use reference files (step 5) first when available — they're faster than running CLI commands.
34
- 7. **Create pages** — write `.jay-html` files under `src/pages/` following directory-based routing. For static override routes (a static page that overrides a dynamic route for a specific URL), declare params with `<script type="application/jay-params">`.
34
+ 7. **Create pages** — write `.jay-html` files under `src/pages/` following directory-based routing. For static override routes (a static page that overrides a dynamic route for a specific URL), declare params with `<script type="application/jay-params">`. Jay-html `if` and `{…}` bind to **tag names only** — no `.length`, method calls, or bracket indexing; derive booleans and counts in `page.ts` (see [jay-html-template-syntax.md](jay-html-template-syntax.md#expression-limits-important)).
35
35
  8. **Validate** — run `jay-stack validate` to check for errors.
36
36
  9. **Test** — run `jay-stack dev --test-mode` and verify pages render.
37
37
 
@@ -41,14 +41,16 @@ There is no standalone "interactive" phase. Any tag with `type: interactive` (re
41
41
  | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
42
42
  | [project-structure.md](project-structure.md) | Project layout, styling patterns (CSS themes, design tokens), configuration files |
43
43
  | [jay-html-syntax.md](jay-html-syntax.md) | Jay-HTML overview: philosophy, component types, nesting rules, links to sub-files |
44
- | [jay-html-template-syntax.md](jay-html-template-syntax.md) | Template markup: data binding, conditions (boolean, enum, numeric, &&/\|\|), loops, refs |
44
+ | [jay-html-template-syntax.md](jay-html-template-syntax.md) | Template markup: data binding, conditions, expression limits (no `.length`), loops, refs |
45
45
  | [jay-html-components.md](jay-html-components.md) | Component imports: headless (key/instance), headfull FS, nesting patterns |
46
46
  | [jay-html-styling.md](jay-html-styling.md) | Styling: inline, external, dynamic style bindings, class bindings |
47
47
  | [routing.md](routing.md) | Directory-based routing: page structure, dynamic routes, route priority |
48
+ | [navigation-patterns.md](navigation-patterns.md) | Active menu/sidebar patterns using `jay.url.path`, `===`, and `^=` operators |
48
49
  | [contracts-and-plugins.md](contracts-and-plugins.md) | Reading contracts, plugin.yaml, .jay-action files, and the materialized indexes |
49
50
  | [Contract Authoring Guide](../contracts/GUIDE.md) | Writing contracts: syntax, page/component/linked contracts, examples |
50
51
  | [script-tags.md](script-tags.md) | Script tag policy: use page.ts for behavior, jay-script="allow" for third-party scripts |
51
52
  | [cli-commands.md](cli-commands.md) | CLI commands: setup, validate, params, action, dev server |
53
+ | [validation-guide.md](validation-guide.md) | Understanding validation output: static analysis, component warnings, how to act on findings |
52
54
  | `../references/<plugin>/` | Pre-generated discovery data: product catalogs, collection schemas (from `jay-stack agent-kit`) |
53
55
 
54
56
  ## When to Use Headfull Components
@@ -279,3 +279,23 @@ Schemas use a compact type notation:
279
279
  | `{tag: sel, type: interactive, elementType: HTMLSelectElement}` | `<select ref="sel">...</select>` |
280
280
  | `{tag: items, type: sub-contract, repeated: true, trackBy: id}` | `<div forEach="items" trackBy="id">...</div>` |
281
281
  | `{tag: detail, type: sub-contract}` | `{detail.fieldName}` |
282
+
283
+ ### Empty list / empty state
284
+
285
+ For repeated lists, add a **boolean variant** (e.g. `hasItems`, `hasCategories`) for empty-state UI. Do **not** use JavaScript property access in jay-html — `if="items.length===0"` fails at runtime because jay-html looks for a tag named `items.length`, not the array's length.
286
+
287
+ ```yaml
288
+ # ✅ In the contract
289
+ - tag: hasCategories
290
+ type: variant
291
+ dataType: boolean
292
+ description: Whether there are any categories
293
+ ```
294
+
295
+ ```html
296
+ <!-- ✅ In jay-html -->
297
+ <p if="!hasCategories">No categories yet.</p>
298
+ <div if="hasCategories" forEach="categories" trackBy="_id">...</div>
299
+ ```
300
+
301
+ Alternatively, expose a **number** data tag (`itemCount`) and use numeric comparison: `if="itemCount===0"`. See [jay-html-template-syntax.md](jay-html-template-syntax.md#expression-limits-important) and [contracts/examples/category-list.md](../contracts/examples/category-list.md).
@@ -39,6 +39,8 @@ Access data and refs with the key prefix:
39
39
 
40
40
  Key-based imports are only available in **pages** (not in headfull FS components).
41
41
 
42
+ **Important:** Do NOT use `<jay:keyName>` for key-based imports. The key is for ViewState access (`{key.field}`), not for inline elements. `<jay:>` tags use the **contract name**, not the key.
43
+
42
44
  ### Pattern 2: Instance-Based (jay: prefix)
43
45
 
44
46
  Multiple instances with props and inline templates. Use when you need **multiple instances** or need to pass **props**.
@@ -99,6 +101,16 @@ Use `{path}` syntax to bind props to values from the page's ViewState. The bindi
99
101
 
100
102
  Inside `<jay:...>`, bindings resolve to **that instance's** contract tags (not the parent).
101
103
 
104
+ ### Choosing between patterns
105
+
106
+ | Need | Pattern | Key? | Tag? |
107
+ | ------------------------------------------------------ | -------------- | --------------- | -------------------------------------------- |
108
+ | One component per page, data across the whole template | Key-based | `key="product"` | No `<jay:>` — use `{product.field}` bindings |
109
+ | Multiple instances, each with own props and template | Instance-based | No key | `<jay:contract-name prop="...">` |
110
+ | One instance but with custom inline template | Instance-based | No key | `<jay:contract-name>` |
111
+
112
+ **Never combine both:** a component imported with `key` cannot also be used as `<jay:>`. These are mutually exclusive patterns.
113
+
102
114
  ### Prop binding summary
103
115
 
104
116
  | Syntax | Resolves to | Example |
@@ -160,18 +172,44 @@ Each headfull component lives in its own subdirectory under `src/components/` wi
160
172
  <jay:SharedHeader logoUrl="/logo.png" />
161
173
  ```
162
174
 
175
+ > **Route params:** Headfull and instance-based headless components do not receive route params directly. To pass a route param, expose it through the page's ViewState and bind it as a prop: `<jay:SideNav activePage="{activePage}" />`. See [routing.md](routing.md) for the full pattern.
176
+
163
177
  ### Component Structure
164
178
 
165
- A headfull component has its own `.jay-html` file with the same structure as a page:
179
+ Each headfull component needs three files in its subdirectory under `src/components/`:
180
+
181
+ **`.jay-contract`** — declares props. Tags are optional (use `tags: []` or omit for structural components):
182
+
183
+ ```yaml
184
+ # components/site-header/site-header.jay-contract
185
+ name: SiteHeader
186
+ props:
187
+ - name: logoUrl
188
+ type: string
189
+ required: true
190
+ ```
191
+
192
+ **`.ts`** — component code. Must use `makeJayStackComponent` with `.withProps()` matching the contract props:
193
+
194
+ ```typescript
195
+ // components/site-header/site-header.ts
196
+ import { makeJayStackComponent, phaseOutput } from '@jay-framework/fullstack-component';
197
+ import type { SiteHeaderContract, SiteHeaderProps } from './site-header.jay-html';
198
+
199
+ export const siteHeader = makeJayStackComponent<SiteHeaderContract>()
200
+ .withProps<SiteHeaderProps>()
201
+ .withFastRender(async (props) => phaseOutput({ logoUrl: props.logoUrl }, {}));
202
+ ```
203
+
204
+ For a structural component with only props and no data logic, `.withFastRender` passes props through as ViewState.
205
+
206
+ **`.jay-html`** — the template:
166
207
 
167
208
  ```html
168
- <!-- components/header/header.jay-html -->
209
+ <!-- components/site-header/site-header.jay-html -->
169
210
  <html>
170
211
  <head>
171
- <script type="application/jay-data">
172
- data:
173
- logoUrl: string
174
- </script>
212
+ <script type="application/jay-data" contract="./site-header.jay-contract"></script>
175
213
  </head>
176
214
  <body>
177
215
  <header>
@@ -6,6 +6,8 @@ Jay-HTML is standard HTML with data bindings. There is no custom component frame
6
6
 
7
7
  The design tool can freely read and rewrite jay-html files as long as contract bindings stay intact. Bindings (`{expression}`, `if`, `forEach`, `ref`) are the only extension to HTML. Everything else — CSS, structure, semantics, accessibility — is native.
8
8
 
9
+ **Common mistake:** expressions are not JavaScript — `if="items.length===0"` fails because jay-html resolves tag names, not property access. Use a boolean tag like `hasItems` or a number tag like `itemCount` instead. See [jay-html-template-syntax.md](jay-html-template-syntax.md#expression-limits-important).
10
+
9
11
  ## Component Types
10
12
 
11
13
  ### Page
@@ -140,15 +140,119 @@ Use parentheses for complex expressions:
140
140
  <button if="count <= 0 || isLoading" disabled>Checkout</button>
141
141
  ```
142
142
 
143
+ ### String Comparison
144
+
145
+ For string-typed tags, the right side of `===`/`!==` resolves as a **field reference**. Use quotes for string literals:
146
+
147
+ ```html
148
+ <!-- Field-to-field comparison -->
149
+ <a if="url === currentPath" class="active">Current page</a>
150
+
151
+ <!-- Literal comparison (use quotes) -->
152
+ <div if="status === 'pending'">Pending approval</div>
153
+ ```
154
+
155
+ For enum-typed tags, the right side remains a variant literal (no quotes needed) — see Enum Variant above.
156
+
157
+ ### Starts With (`^=`)
158
+
159
+ Check if a string starts with a prefix — useful for section-level highlighting in navigation:
160
+
161
+ ```html
162
+ <a if="jay.url.path ^= '/docs/designer'" class="section-active">Designer</a>
163
+ <a if="currentPath ^= sectionUrl" class="active">{label}</a>
164
+ ```
165
+
166
+ Works with both field references and quoted literals.
167
+
168
+ ### Built-in Bindings (`jay.`)
169
+
170
+ The `jay.` prefix provides framework values in page templates:
171
+
172
+ | Binding | Value | Example |
173
+ | -------------- | ------------------------------ | ------------------------ |
174
+ | `jay.params.X` | Route param from `[X]` segment | `jay.params.slug` |
175
+ | `jay.url.path` | Current URL pathname | `/docs/designer/routing` |
176
+
177
+ Available at all render phases (slow, fast, interactive). Use in text bindings, prop bindings, and conditionals:
178
+
179
+ ```html
180
+ <h1>Current: {jay.url.path}</h1>
181
+ <jay:Sidebar activePage="{jay.params.slug}" currentPath="{jay.url.path}" />
182
+ <a if="jay.url.path ^= '/docs'" class="docs-active">Docs</a>
183
+ ```
184
+
185
+ `jay.` bindings are available in **page templates only** — headfull components receive this data via props.
186
+
187
+ ### Choosing the Right Condition Type
188
+
189
+ The contract determines how conditions work. Choose the data type based on the use case:
190
+
191
+ | Use case | Contract type | Condition syntax | Example |
192
+ | ----------------------------- | ---------------- | -------------------- | -------------------------- |
193
+ | Show/hide a section | `boolean` | `if="flag"` | `if="inStock"` |
194
+ | Switch between known states | `variant` (enum) | `if="tag === value"` | `if="status === active"` |
195
+ | Compare against another field | `string` | `if="a === b"` | `if="url === currentPath"` |
196
+ | Compare against a literal | `string` | `if="a === 'text'"` | `if="role === 'admin'"` |
197
+ | Match URL prefix | `string` | `if="a ^= b"` | `if="path ^= '/docs'"` |
198
+ | Threshold / range | `number` | `if="a > n"` | `if="count > 0"` |
199
+
200
+ **Boolean** — best for simple on/off states. The contract declares `type: variant, dataType: boolean`. Most conditions are booleans.
201
+
202
+ **Enum** — best when there's a fixed set of known values (e.g., `active | inactive | pending`). The compiler validates that the value exists in the enum. Use for mutually exclusive states where each value maps to different UI.
203
+
204
+ **String** — best for dynamic comparison where values aren't known at contract time (URLs, slugs, user input). The right side of `===` resolves as a field reference; use quotes for literals. Also supports `^=` for prefix matching.
205
+
143
206
  ### Rules Summary
144
207
 
145
208
  - Boolean: `if="flag"` / `if="!flag"`
146
- - Enum: `if="tag===value"` / `if="tag!==value"` (no quotes around value)
209
+ - Enum: `if="tag === value"` / `if="tag !== value"` (no quotes around value)
210
+ - String field: `if="url === currentPath"` (bare identifier = field)
211
+ - String literal: `if="url === '/about'"` (quoted = literal)
212
+ - Starts with: `if="path ^= prefix"` / `if="path ^= '/docs'"`
147
213
  - Numeric: `if="count > 0"`, `if="price <= budget"`
148
214
  - Field comparison: `if="available >= required"`
149
215
  - Logical: `if="a && b"`, `if="a || b"`, `if="(a || b) && c"`
150
216
  - Negation: `!` prefix on booleans
151
217
 
218
+ ### Expression limits (important)
219
+
220
+ Jay-html expressions resolve **contract or page tag names only**. They are **not** JavaScript — even when the syntax looks similar (`===`, `&&`, `> 0`).
221
+
222
+ ❌ **Invalid** — property access, indexing, or method calls:
223
+
224
+ ```html
225
+ <p if="items.length===0">No items</p>
226
+ <!-- looks for a tag named "items.length" -->
227
+ <span>{user.name.split(' ')[0]}</span>
228
+ <!-- no method calls or bracket indexing -->
229
+ ```
230
+
231
+ ✅ **Valid** — expose derived state in the contract or in `page.ts` ViewState:
232
+
233
+ ```html
234
+ <p if="!hasItems">No items</p>
235
+ <span if="itemCount > 0">You have {itemCount} items</span>
236
+ ```
237
+
238
+ | Need | Contract / ViewState tag | Jay-HTML |
239
+ | --------------- | ---------------------------- | -------------------- |
240
+ | Empty list hint | `hasItems: boolean` variant | `if="!hasItems"` |
241
+ | Count-based UI | `itemCount: number` data tag | `if="itemCount===0"` |
242
+
243
+ See [contracts/examples/category-list.md](../contracts/examples/category-list.md) (`hasCategories`).
244
+
245
+ ### Common Errors
246
+
247
+ Invalid expressions produce a visible `[INVALID: expression]` marker in the page instead of crashing. The validation message includes the parse error and a pointer to this guide.
248
+
249
+ | Error | Cause | Fix |
250
+ | ------------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------- |
251
+ | `Expected "." or identifier` | Curly braces in non-expression context (e.g., CSS in body `<style>`) | Move `<style>` to `<head>` or use body styles per DL#164 |
252
+ | `unexpected operator "^="` | Using `^=` with an older framework version | Update framework or use `===` instead |
253
+ | `Unknown enum value "X"` | Comparing against a value not in the enum | Check the contract for valid enum values |
254
+ | `the data field [X] not found` | Referencing a field not in the contract | Check the contract tags for available fields |
255
+
152
256
  ## Loops (forEach / trackBy)
153
257
 
154
258
  Iterate over repeated sub-contracts:
@@ -0,0 +1,73 @@
1
+ # Navigation Patterns
2
+
3
+ ## Active Menu Item
4
+
5
+ Use conditional class bindings with `===` to highlight the current page:
6
+
7
+ ```html
8
+ <nav>
9
+ <div forEach="menuItems" trackBy="url">
10
+ <a href="{url}" class="nav-link {url === currentPath ? active}">{label}</a>
11
+ </div>
12
+ </nav>
13
+ ```
14
+
15
+ The `{url === currentPath ? active}` adds the `active` class when the menu item's URL matches the current path.
16
+
17
+ ## Active Section
18
+
19
+ Use `^=` (starts with) for section-level highlighting. A link to `/docs/designer` should be active when the URL is `/docs/designer/routing`:
20
+
21
+ ```html
22
+ <nav>
23
+ <div forEach="sections" trackBy="url">
24
+ <a href="{url}" class="section-link {currentPath ^= url ? active}">{label}</a>
25
+ </div>
26
+ </nav>
27
+ ```
28
+
29
+ ## Two-Level Sidebar
30
+
31
+ Combine section matching and page matching for a sidebar with expandable sections:
32
+
33
+ ```html
34
+ <nav>
35
+ <div forEach="sections" trackBy="url">
36
+ <a href="{url}" class="section {currentPath ^= url ? active}">{label}</a>
37
+ <div if="currentPath ^= url" forEach="pages" trackBy="url">
38
+ <a href="{url}" class="page {url === currentPath ? active}">{label}</a>
39
+ </div>
40
+ </div>
41
+ </nav>
42
+ ```
43
+
44
+ - Section links use `^=` — active when any child page is current
45
+ - Page links within the section use `===` — active on exact match
46
+ - The `if="currentPath ^= url"` on the pages container shows child pages only for the active section
47
+
48
+ ## Passing URL Data to Components
49
+
50
+ In page templates, use `jay.` bindings to pass URL information to nested components without needing a `page.ts`:
51
+
52
+ ```html
53
+ <jay:Sidebar currentPath="{jay.url.path}" activeRole="{jay.params.role}" />
54
+ ```
55
+
56
+ Inside the component template, use the props as ViewState fields:
57
+
58
+ ```html
59
+ <!-- sidebar.jay-html -->
60
+ <a href="/docs/designer" class="role-link {currentPath ^= '/docs/designer' ? active}">Designer</a>
61
+ <a href="/docs/developer" class="role-link {currentPath ^= '/docs/developer' ? active}"
62
+ >Developer</a
63
+ >
64
+ ```
65
+
66
+ ## Operators
67
+
68
+ | Operator | Meaning | Use for |
69
+ | -------- | ----------- | --------------------------- |
70
+ | `===` | Exact match | Current page highlighting |
71
+ | `^=` | Starts with | Section/parent highlighting |
72
+
73
+ Both work with field references (bare identifier) and quoted string literals (`'/docs'`).
@@ -144,6 +144,61 @@ src/pages/products/[slug]/page.jay-html
144
144
 
145
145
  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`.
146
146
 
147
+ ### Passing Route Params to Nested Components
148
+
149
+ Route params flow automatically to keyed headless components that declare them as `params` in their contract. Instance-based headless components and headfull components do not receive route params directly — they receive props from the template.
150
+
151
+ #### Direct binding with `jay.params` (no page.ts needed)
152
+
153
+ Use `jay.params.X` to bind route params directly to nested component props:
154
+
155
+ ```html
156
+ <!-- src/pages/docs/[role]/[slug]/page.jay-html -->
157
+ <jay:DocsSidebar activeRole="{jay.params.role}" activePage="{jay.params.slug}" />
158
+ ```
159
+
160
+ No `page.ts`, no page contract needed for param passing. `jay.params` is available at all render phases. Use `jay.url.path` for the full URL pathname:
161
+
162
+ ```html
163
+ <jay:Sidebar currentPath="{jay.url.path}" />
164
+ ```
165
+
166
+ See [jay-html-template-syntax.md](jay-html-template-syntax.md) for the full list of `jay.` bindings and [navigation-patterns.md](navigation-patterns.md) for active menu patterns.
167
+
168
+ #### Passing via page.ts (when you need data transformation)
169
+
170
+ When route params need processing before reaching the component (e.g., fetching data, computing derived values), use the `page.ts` passthrough pattern:
171
+
172
+ **1. Page contract exposes the param as ViewState:**
173
+
174
+ ```yaml
175
+ # page.jay-contract
176
+ name: Page
177
+ params:
178
+ slug: string
179
+ tags:
180
+ - tag: activePage
181
+ type: data
182
+ dataType: string
183
+ phase: slow
184
+ ```
185
+
186
+ **2. `page.ts` passes the param into ViewState:**
187
+
188
+ ```typescript
189
+ .withSlowlyRender(async (props) =>
190
+ phaseOutput({ activePage: props.slug }, {})
191
+ )
192
+ ```
193
+
194
+ **3. Template binds ViewState to the nested component prop:**
195
+
196
+ ```html
197
+ <jay:SideNav activePage="{activePage}" />
198
+ ```
199
+
200
+ The same pattern works with keyed headless data — if a keyed component already provides the value, bind directly: `<jay:SideNav activePage="{product.slug}" />`.
201
+
147
202
  ### Discovering Param Values
148
203
 
149
204
  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:
@@ -153,7 +208,7 @@ jay-stack params wix-stores/product-page
153
208
  # Output: [{"slug": "blue-shirt"}, {"slug": "red-hat"}, ...]
154
209
  ```
155
210
 
156
- Params are always strings (URL params).
211
+ Params are always strings (URL params). Routes are **case-sensitive** — a slug of `My-Page` produces the URL `/my-page` only if the param value is exactly `my-page`. Use lowercase for all param values and filenames that become URL segments.
157
212
 
158
213
  ## Query Parameters
159
214
 
@@ -0,0 +1,88 @@
1
+ # Understanding Validation Output
2
+
3
+ ## How Validation Works
4
+
5
+ `jay-stack validate` runs static analysis on `.jay-html` files. It parses each template and checks it against rules from installed validator plugins (SEO, a11y, design-system).
6
+
7
+ **Key concept: static analysis sees the template, not the rendered page.** The validator checks what's written in the `.jay-html` file — it cannot see:
8
+
9
+ - Dynamic content from `{bindings}` (e.g., `{post.content}` may contain images and headings)
10
+ - Data fetched at render time from services
11
+ - Content generated by other tools or scripts
12
+
13
+ ## What the Validator Sees
14
+
15
+ The validator processes each `.jay-html` file with:
16
+
17
+ - **Page template** — your HTML, bindings, and inline styles
18
+ - **Headfull component content** — `<jay:SiteHeader />`, `<jay:DocsSidebar />` etc. are expanded into the page before validation
19
+ - **Headless component tags** — `<jay:product-widget>` inline templates are visible
20
+ - **Linked CSS files** — `<link rel="stylesheet" href="...">` referenced from `<head>` are read and validated (e.g., for CSS `@import` of external URLs)
21
+ - **Inline `<style>` blocks** — CSS in `<head>` is parsed for rule violations
22
+
23
+ So a warning about an `<img>` might come from a headfull component's template, not your page template. A CSS warning might come from a linked stylesheet.
24
+
25
+ ## How to Read Warnings
26
+
27
+ Each warning has:
28
+
29
+ - **Message** — what was found and why it matters
30
+ - **Suggestion** — how to fix it
31
+ - **Element** — which HTML element triggered it (some include the full tag with attributes)
32
+
33
+ ### Acting on Warnings
34
+
35
+ 1. **Check if the element is in your template** — search your `.jay-html` file for the element. If it's not there, it comes from a headfull component.
36
+
37
+ 2. **For headfull component warnings** — fix them in the component's `.jay-html` file (e.g., `src/components/site-header/site-header.jay-html`), not in the page that imports it.
38
+
39
+ 3. **For dynamic content warnings** — if the warning is about content inside a `{binding}` (like `{post.content}`), you can't fix it in the template. The content is generated at render time. These are usually false positives.
40
+
41
+ 4. **Don't loop** — if a warning can't be fixed in any template file, it's a validator limitation. Move on.
42
+
43
+ ## Common Scenarios
44
+
45
+ ### "Image without loading attribute" from a component
46
+
47
+ The image is in a headfull component (e.g., sidebar icons). Fix it in the component's `.jay-html`, not the page.
48
+
49
+ ### "Heading level skipped" with dynamic content
50
+
51
+ Your page has `<h1>` and a component has `<h3>`, but the dynamic content (markdown) provides `<h2>` at render time. If the heading hierarchy is correct in the rendered page, fix the component to use the right level — don't restructure the page.
52
+
53
+ ### "No fetchpriority=high" on a text-only page
54
+
55
+ Your page has no large images in the template. Small icons (under 200px) don't need fetchpriority. This warning only fires for large images.
56
+
57
+ ### Warnings on component files
58
+
59
+ Components are fragments — they don't need `<h1>`, `<main>`, `<title>`, or `<meta>`. These page-level rules are skipped for files in `src/components/`. If you still see them, check the file path.
60
+
61
+ ## Suppressing Warnings
62
+
63
+ Each warning's suggestion tells you exactly what to add or change to suppress it. The general suppression mechanisms are:
64
+
65
+ ### Add the missing attribute
66
+
67
+ Most warnings are suppressed by adding the attribute the rule checks for. The suggestion tells you which attribute and what values are accepted. For example, `loading="lazy"` or `loading="eager"` both suppress the image loading warning.
68
+
69
+ ### Design system: inline comment
70
+
71
+ For design-system token warnings, add `/* design-system: allow */` as a comment on the same CSS line:
72
+
73
+ ```css
74
+ padding: 96px 0; /* design-system: allow */
75
+ ```
76
+
77
+ ### When you can't suppress
78
+
79
+ If a warning comes from dynamic content (`{post.content}`) or a generated file, you can't suppress it in the template. This is a validator limitation — the warning is a false positive. Don't loop trying to fix it.
80
+
81
+ ## Running Validation
82
+
83
+ ```bash
84
+ jay-stack validate # validate all pages
85
+ jay-stack validate --strict # treat warnings as errors
86
+ ```
87
+
88
+ Validation runs automatically during `jay-stack build`. Warnings don't block the build; errors do (with `--strict`).
@@ -153,7 +153,18 @@ jay-stack params wix-stores/product-page
153
153
  # Output: [{"slug": "blue-shirt"}, {"slug": "red-hat"}, ...]
154
154
  ```
155
155
 
156
- Params are always strings (URL params).
156
+ Params are always strings (URL params). Routes are **case-sensitive** — use lowercase for all param values and filenames that become URL segments.
157
+
158
+ ### When page.ts is not needed for params
159
+
160
+ The designer can bind route params directly to nested component props using `jay.params` in the template — no `page.ts` or page contract needed:
161
+
162
+ ```html
163
+ <jay:DocsSidebar activeRole="{jay.params.role}" activePage="{jay.params.slug}" />
164
+ <jay:Sidebar currentPath="{jay.url.path}" />
165
+ ```
166
+
167
+ Only create a `page.ts` for params when you need to **transform** them (fetch data, compute derived values, combine with service calls). If the page just passes params through to components, `jay.params` is sufficient.
157
168
 
158
169
  ## Query Parameters
159
170
 
@@ -13,7 +13,7 @@ A plugin provides headless components (data + interactions, no UI) that project
13
13
  3. **Define actions** with `.jay-action` metadata
14
14
  4. **Optionally add routes** — pages for admin tools and dashboards
15
15
  5. **Optionally add validators** — custom jay-html validation rules
16
- 6. **Optionally add setup/agentkit handlers** — config templating, add-menu generation
16
+ 6. **Optionally add setup/agentkit handlers** — config templating, add-menu generation, AIditor Project settings tabs
17
17
  7. **Set up `plugin.yaml`** — list contracts, actions, services, contexts, routes, validators, setup, agentkit
18
18
  8. **Configure build** — dual entry points (server + client), vite.config.ts, package.json exports
19
19
  9. **Validate** with `jay-stack validate-plugin`
@@ -26,7 +26,7 @@ The plugin participates in four CLI commands, each running different hooks:
26
26
  | --------------------------- | ------------------ | ---------------------------------------------------------------------------------- |
27
27
  | `jay-stack validate-plugin` | Plugin development | Checks plugin.yaml structure, contracts, exports, handler references |
28
28
  | `jay-stack setup <plugin>` | Project setup | `setup` — creates config files, validates credentials |
29
- | `jay-stack agent-kit` | Before development | `agentkit` — generates add-menu items, reference data, skills, thumbnails |
29
+ | `jay-stack agent-kit` | Before development | `agentkit` — generates add-menu items, settings tabs, reference data, skills |
30
30
  | `jay-stack validate` | During development | `validators[].handler` — runs your validation rules against project jay-html files |
31
31
 
32
32
  **`validate-plugin`** validates YOUR plugin's structure. Run it during plugin development.
@@ -34,27 +34,29 @@ The plugin participates in four CLI commands, each running different hooks:
34
34
 
35
35
  ## Guides
36
36
 
37
- | File | Topic |
38
- | ------------------------------------------------- | ----------------------------------------------------------------------- |
39
- | [Contract Authoring Guide](../contracts/GUIDE.md) | Writing contracts: syntax, page/component/linked contracts, examples |
40
- | [contracts-guide.md](contracts-guide.md) | Plugin-specific contract concerns |
41
- | [plugin-structure.md](plugin-structure.md) | plugin.yaml, package layout, exports |
42
- | [component-structure.md](component-structure.md) | makeJayStackComponent, builder API, three-phase rendering |
43
- | [component-state.md](component-state.md) | createSignal, createMemo, createEffect, createDerivedArray, createEvent |
44
- | [component-refs.md](component-refs.md) | Refs, collection refs, element types |
45
- | [component-data.md](component-data.md) | Immutable data, JSON Patch, createPatchableSignal |
46
- | [component-context.md](component-context.md) | Context hooks: provide, reactive, global |
47
- | [render-results.md](render-results.md) | phaseOutput, RenderPipeline, errors, redirects |
48
- | [actions-guide.md](actions-guide.md) | makeJayAction, makeJayQuery, .jay-action files |
49
- | [webhooks-guide.md](webhooks-guide.md) | makeWebhook, data change invalidation, renderer server |
50
- | [services-guide.md](services-guide.md) | createJayService, makeJayInit |
51
- | [plugin-routes.md](plugin-routes.md) | Plugin-provided pages: routes, jay-html templates, page components |
52
- | [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
53
- | [commands-guide.md](commands-guide.md) | makeCliCommand, .jay-command files, CONSOLE_CONTEXT, jay-stack run |
54
- | [validation.md](validation.md) | jay-stack validate-plugin, writing custom jay-html validators |
55
- | [setup-guide.md](setup-guide.md) | Setup handlers, references handlers, add-menu generation |
56
- | [dev-server-service.md](dev-server-service.md) | Dev server service API: routes, params, freeze management |
57
- | `../references/<plugin>/` | Plugin reference data |
37
+ | File | Topic |
38
+ | ------------------------------------------------------ | ----------------------------------------------------------------------- |
39
+ | [Contract Authoring Guide](../contracts/GUIDE.md) | Writing contracts: syntax, page/component/linked contracts, examples |
40
+ | [contracts-guide.md](contracts-guide.md) | Plugin-specific contract concerns |
41
+ | [plugin-structure.md](plugin-structure.md) | plugin.yaml, package layout, exports |
42
+ | [component-structure.md](component-structure.md) | makeJayStackComponent, builder API, three-phase rendering |
43
+ | [component-state.md](component-state.md) | createSignal, createMemo, createEffect, createDerivedArray, createEvent |
44
+ | [component-refs.md](component-refs.md) | Refs, collection refs, element types |
45
+ | [component-data.md](component-data.md) | Immutable data, JSON Patch, createPatchableSignal |
46
+ | [component-context.md](component-context.md) | Context hooks: provide, reactive, global |
47
+ | [render-results.md](render-results.md) | phaseOutput, RenderPipeline, errors, redirects |
48
+ | [actions-guide.md](actions-guide.md) | makeJayAction, makeJayQuery, .jay-action files |
49
+ | [webhooks-guide.md](webhooks-guide.md) | makeWebhook, data change invalidation, renderer server |
50
+ | [services-guide.md](services-guide.md) | createJayService, makeJayInit |
51
+ | [plugin-routes.md](plugin-routes.md) | Plugin-provided pages: routes, jay-html templates, page components |
52
+ | [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
53
+ | [commands-guide.md](commands-guide.md) | makeCliCommand, .jay-command files, CONSOLE_CONTEXT, jay-stack run |
54
+ | [validation.md](validation.md) | jay-stack validate-plugin, writing custom jay-html validators |
55
+ | [setup-guide.md](setup-guide.md) | Setup handlers, agent-kit handlers, references generation |
56
+ | [add-menu-guide.md](add-menu-guide.md) | AIditor add-menu items: schema, interaction, browse, presentation |
57
+ | [aiditor-settings-guide.md](aiditor-settings-guide.md) | AIditor Project settings tabs: template, materialization, devOnly route |
58
+ | [dev-server-service.md](dev-server-service.md) | Dev server service API: routes, params, freeze management |
59
+ | `../references/<plugin>/` | Plugin reference data |
58
60
 
59
61
  ## Key Principles
60
62