@jay-framework/jay-stack-cli 0.25.0 → 0.25.2
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/contracts/GUIDE.md +8 -8
- package/agent-kit-template/contracts/linked-contracts.md +1 -1
- package/agent-kit-template/designer/INSTRUCTIONS.md +1 -0
- package/agent-kit-template/designer/cli-commands.md +7 -0
- package/agent-kit-template/designer/contracts-and-plugins.md +1 -1
- package/agent-kit-template/designer/error-pages-guide.md +109 -0
- package/agent-kit-template/designer/jay-html-template-syntax.md +1 -1
- package/agent-kit-template/designer/routing.md +7 -0
- package/agent-kit-template/designer/validation-guide.md +27 -3
- package/agent-kit-template/designer/validation-tier-2-guide.md +64 -0
- package/agent-kit-template/developer/INSTRUCTIONS.md +1 -0
- package/agent-kit-template/developer/error-pages-guide.md +86 -0
- package/agent-kit-template/developer/render-results.md +16 -0
- package/agent-kit-template/developer/seo-guide.md +110 -1
- package/agent-kit-template/plugin/validation.md +9 -0
- package/dist/{index-l6QVpvnY.js → index-B5oavmfA.js} +906 -710
- package/dist/index.d.ts +34 -1
- package/dist/index.js +1023 -21
- package/package.json +14 -13
|
@@ -60,11 +60,11 @@ See [syntax.md](syntax.md) for the full YAML format: tag types, phases, data typ
|
|
|
60
60
|
|
|
61
61
|
Start with the simplest example that matches your use case, then add complexity as needed:
|
|
62
62
|
|
|
63
|
-
| Example
|
|
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 |
|
|
63
|
+
| Example | Complexity | Key patterns |
|
|
64
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------- |
|
|
65
|
+
| [mini-cart](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/mini-cart.md) | Trivial | Variant + interactive refs |
|
|
66
|
+
| [cart-indicator](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/cart-indicator.md) | Simple | Flat data, variants, phase choices |
|
|
67
|
+
| [category-list](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/category-list.md) | Medium | Props, repeated sub-contracts |
|
|
68
|
+
| [product-card](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/product-card.md) | Medium-complex | Linked sub-contracts, variants, multiple ref types |
|
|
69
|
+
| [product-page](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/product-page.md) | Complex | Params, nested repeated sub-contracts, linked sub-contracts |
|
|
70
|
+
| [composing-contracts](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/composing-contracts.md) | Pattern | How contracts link together into a hierarchy |
|
|
@@ -72,4 +72,4 @@ The linked contract must have a tag matching the `trackBy` field.
|
|
|
72
72
|
|
|
73
73
|
## See Also
|
|
74
74
|
|
|
75
|
-
- [composing-contracts example](examples/composing-contracts.md) — Real-world composition hierarchy from wix-stores
|
|
75
|
+
- [composing-contracts example](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/composing-contracts.md) — Real-world composition hierarchy from wix-stores
|
|
@@ -46,6 +46,7 @@ There is no standalone "interactive" phase. Any tag with `type: interactive` (re
|
|
|
46
46
|
| [design-system-guide.md](design-system-guide.md) | Design-system elements — **create** (author template + contract, `jay-content` slots) and **use** (flatten with `template=`, drift, `override` facets, `sync`) |
|
|
47
47
|
| [jay-html-styling.md](jay-html-styling.md) | Styling: inline, external, dynamic style bindings, class bindings |
|
|
48
48
|
| [routing.md](routing.md) | Directory-based routing: page structure, dynamic routes, route priority |
|
|
49
|
+
| [error-pages-guide.md](error-pages-guide.md) | Error pages: reserved `404`/`5xx` status folders, the `error` headless, bindings, exact→family resolution |
|
|
49
50
|
| [navigation-patterns.md](navigation-patterns.md) | Active menu/sidebar patterns using `jay.url.path`, `===`, and `^=` operators |
|
|
50
51
|
| [contracts-and-plugins.md](contracts-and-plugins.md) | Reading contracts, plugin.yaml, .jay-action files, and the materialized indexes |
|
|
51
52
|
| [Contract Authoring Guide](../contracts/GUIDE.md) | Writing contracts: syntax, page/component/linked contracts, examples |
|
|
@@ -69,8 +69,15 @@ jay-stack validate -v
|
|
|
69
69
|
|
|
70
70
|
# JSON output
|
|
71
71
|
jay-stack validate --json
|
|
72
|
+
|
|
73
|
+
# Tier 2: deeper checks against an existing build (needs `jay-stack build` first)
|
|
74
|
+
jay-stack validate --tier-2
|
|
72
75
|
```
|
|
73
76
|
|
|
77
|
+
The default run is **Tier 1** (fast, template-only). `--tier-2` adds deep checks against a build's output
|
|
78
|
+
(dynamic-slug links, rendered-content links, per-instance meta) — see
|
|
79
|
+
[validation-tier-2-guide.md](validation-tier-2-guide.md).
|
|
80
|
+
|
|
74
81
|
Example output:
|
|
75
82
|
|
|
76
83
|
```
|
|
@@ -327,4 +327,4 @@ For repeated lists, add a **boolean variant** (e.g. `hasItems`, `hasCategories`)
|
|
|
327
327
|
<div if="hasCategories" forEach="categories" trackBy="_id">...</div>
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
-
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](
|
|
330
|
+
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](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/category-list.md).
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Error Pages
|
|
2
|
+
|
|
3
|
+
Error pages render your site's chrome when a request fails — a 404 for an unknown URL, a 5xx for a
|
|
4
|
+
server error — instead of a bare plain-text message. You author them exactly like any other page,
|
|
5
|
+
in a reserved top-level folder named for the HTTP status they cover.
|
|
6
|
+
|
|
7
|
+
## Where they live
|
|
8
|
+
|
|
9
|
+
Create error pages in `src/pages/` in a folder whose name is an HTTP **status token**:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/pages/
|
|
13
|
+
404/page.jay-html # exact: 404 Not Found
|
|
14
|
+
5xx/page.jay-html # family: any 500–599
|
|
15
|
+
502/page.jay-html # exact: 502 Bad Gateway (optional, overrides 5xx for 502)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A status token is either:
|
|
19
|
+
|
|
20
|
+
- **Exact** — three digits, `100`–`599` (e.g. `404`, `502`).
|
|
21
|
+
- **Family** — a leading digit then `xx` (e.g. `4xx`, `5xx`), covering that whole class.
|
|
22
|
+
|
|
23
|
+
v1 renders only **4xx** and **5xx**. Reserve others with a folder, but they are not served yet.
|
|
24
|
+
|
|
25
|
+
These folders are **not navigable routes** — visiting `/404` or `/5xx` directly returns a normal 404,
|
|
26
|
+
and they never appear in the sitemap or internal-link checks. The error page is selected by the
|
|
27
|
+
response **status**, not by its URL.
|
|
28
|
+
|
|
29
|
+
## Resolution: exact → family → plain text
|
|
30
|
+
|
|
31
|
+
For a given status, Jay picks the most specific page:
|
|
32
|
+
|
|
33
|
+
1. **Exact** match — `pages/502/` for a 502.
|
|
34
|
+
2. **Family** fallback — `pages/5xx/` for a 502 when there's no `pages/502/`.
|
|
35
|
+
3. **Plain text** — if neither exists, a built-in plain-text response.
|
|
36
|
+
|
|
37
|
+
So a single `pages/5xx/` covers every server error, and you add `pages/502/` only where you want
|
|
38
|
+
bespoke copy. Minimum recommended set: **`404` and `5xx`** (the `validate` command warns if either is
|
|
39
|
+
missing — see below).
|
|
40
|
+
|
|
41
|
+
## Authoring an error page
|
|
42
|
+
|
|
43
|
+
Import the built-in `error` headless contract and bind its fields. No plugin install is needed —
|
|
44
|
+
`jay-stack`/`error` is built in:
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<html>
|
|
48
|
+
<head>
|
|
49
|
+
<script
|
|
50
|
+
type="application/jay-headless"
|
|
51
|
+
plugin="jay-stack"
|
|
52
|
+
contract="error"
|
|
53
|
+
key="error"
|
|
54
|
+
></script>
|
|
55
|
+
<script type="application/jay-data">
|
|
56
|
+
data:
|
|
57
|
+
</script>
|
|
58
|
+
<title>{error.title}</title>
|
|
59
|
+
<meta name="description" content="{error.message}" />
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<main class="error-page">
|
|
63
|
+
<h1>{error.status} — {error.title}</h1>
|
|
64
|
+
<p>{error.message}</p>
|
|
65
|
+
<ul forEach="error.details" trackBy="key">
|
|
66
|
+
<li>{key}: {value}</li>
|
|
67
|
+
</ul>
|
|
68
|
+
<a href="/">Go home</a>
|
|
69
|
+
</main>
|
|
70
|
+
</body>
|
|
71
|
+
</html>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Available bindings
|
|
75
|
+
|
|
76
|
+
| Binding | Type | Meaning |
|
|
77
|
+
| ----------------- | ------------------ | --------------------------------------------------------- |
|
|
78
|
+
| `{error.status}` | number | The HTTP status (e.g. `404`, `503`). |
|
|
79
|
+
| `{error.title}` | string | Short headline — supplied by logic or a sensible default. |
|
|
80
|
+
| `{error.message}` | string | Longer explanation — supplied or defaulted. |
|
|
81
|
+
| `{error.code}` | string | App-specific error code; `''` when none. |
|
|
82
|
+
| `error.details` | `{ key, value }[]` | Key/value pairs for `forEach` — empty when none. |
|
|
83
|
+
|
|
84
|
+
`error.title` and `error.message` always have a value: when the developer's code supplies none, Jay
|
|
85
|
+
fills a per-status default (e.g. `404 → "Page Not Found"`). You can rely on them being present.
|
|
86
|
+
|
|
87
|
+
## No fast/slow data phase
|
|
88
|
+
|
|
89
|
+
An error page is a **pass-through** of the injected `error` data — it runs **no** author fast or slow
|
|
90
|
+
phase and cannot fetch its own request-time data (rendering an error page must never itself fail). Its
|
|
91
|
+
static chrome (layout, nav, copy) is still pre-rendered at build. Keep the page to static markup plus
|
|
92
|
+
the `error.*` bindings.
|
|
93
|
+
|
|
94
|
+
## Validation
|
|
95
|
+
|
|
96
|
+
`jay-stack validate` warns when the minimum pages (`404` and `5xx`) are absent — a `4xx` family page
|
|
97
|
+
counts as covering `404`. If your site intentionally accepts the plain-text fallback, suppress the
|
|
98
|
+
nudge in `.jay`:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
validations:
|
|
102
|
+
allow-no-error-pages: true
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## See also
|
|
106
|
+
|
|
107
|
+
- Developer side — returning enriched errors (`notFound`, `serverError5xx`, title/details):
|
|
108
|
+
`../developer/error-pages-guide.md`.
|
|
109
|
+
- Template syntax (`forEach`, bindings, expression limits): `jay-html-template-syntax.md`.
|
|
@@ -240,7 +240,7 @@ Jay-html expressions resolve **contract or page tag names only**. They are **not
|
|
|
240
240
|
| Empty list hint | `hasItems: boolean` variant | `if="!hasItems"` |
|
|
241
241
|
| Count-based UI | `itemCount: number` data tag | `if="itemCount===0"` |
|
|
242
242
|
|
|
243
|
-
See [contracts/examples/category-list.md](
|
|
243
|
+
See [contracts/examples/category-list.md](https://github.com/jay-framework/jay/blob/main/packages/jay-stack/stack-cli/agent-kit-template/contracts/examples/category-list.md) (`hasCategories`).
|
|
244
244
|
|
|
245
245
|
### Common Errors
|
|
246
246
|
|
|
@@ -39,6 +39,13 @@ Static routes match before dynamic routes (most specific first):
|
|
|
39
39
|
3. **`[[param]]`** — optional param
|
|
40
40
|
4. **`[...param]`** — catch-all — lowest priority
|
|
41
41
|
|
|
42
|
+
## Reserved Status Folders (Error Pages)
|
|
43
|
+
|
|
44
|
+
Top-level folders named for an HTTP **status token** — `404`, `5xx`, `502`, `4xx`, … — are reserved
|
|
45
|
+
for **error pages**, not navigable routes. They are selected by response status (not URL), never
|
|
46
|
+
appear in the sitemap, and visiting `/404` directly returns a normal 404. See
|
|
47
|
+
[error-pages-guide.md](error-pages-guide.md).
|
|
48
|
+
|
|
42
49
|
## Static Route Overrides
|
|
43
50
|
|
|
44
51
|
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:
|
|
@@ -27,6 +27,13 @@ So a warning about an `<img>` might come from a full-stack component's template,
|
|
|
27
27
|
- **Errors** block the build. They must be fixed — there is no way to suppress them.
|
|
28
28
|
- **Warnings** must be either fixed or explicitly suppressed. Do not ignore warnings — each one has a clear resolution path (add the missing attribute, or suppress via `<script type="application/jay-validations">`).
|
|
29
29
|
|
|
30
|
+
**Broken internal links are errors** (DL#210). `validate` resolves every `<a href>` against the project's
|
|
31
|
+
**static routes + public assets**. An href that resolves to nothing is an error: a placeholder (`href="#"` or
|
|
32
|
+
empty), a typo, or a link to a page/asset that does not exist. Links that match a **dynamic** route pattern
|
|
33
|
+
(e.g. `/blog/[slug]`) are **not** checked here — the concrete slug list isn't known at validate time — so they
|
|
34
|
+
never false-error. External URLs, `mailto:`/`tel:`, in-page `#fragments`, and `{binding}` hrefs are skipped.
|
|
35
|
+
Fix the path, use `<button>` for JS-driven anchors, or suppress per-page (see the `jay-stack` table below).
|
|
36
|
+
|
|
30
37
|
One design-system rule is a **hard error**, not a warning: `REGION-CSS-NO-REF` (DL#209) — a `<jay:X>` region
|
|
31
38
|
whose `template=` ships CSS but which has no `ref=`. Without a `ref` the CSS has no scope anchor, so `sync`
|
|
32
39
|
silently drops it; the fix is to add a `ref` (see `design-system-guide.md` → _The shape of a region's CSS_).
|
|
@@ -126,6 +133,7 @@ The core `jay-stack validate` emits four warnings that nudge you to reuse UI thr
|
|
|
126
133
|
| `REGION-OVERRIDE-NON-CONTENT` | `jay-validations="REGION-OVERRIDE-NON-CONTENT"` on the import |
|
|
127
134
|
| `COMPONENT-NO-TEMPLATE` | `jay-stack: allow-no-template: [Contract]` |
|
|
128
135
|
| `NO-DESIGN-SYSTEM` | `jay-stack: allow-no-design-system: true` |
|
|
136
|
+
| Broken internal link (DL#210) | `jay-stack: allow-broken-links: true` (per-page — skips all link checks for that page) |
|
|
129
137
|
|
|
130
138
|
```html
|
|
131
139
|
<script type="application/jay-validations">
|
|
@@ -167,12 +175,28 @@ By default the scorecard prints **one-line totals** for the whole project:
|
|
|
167
175
|
Run with `-v` / `--verbose` to expand the **per-page** breakdown (coverage per file, which tags are unused,
|
|
168
176
|
per-template reuse counts, and `⚠ low` flags on pages under 50% coverage).
|
|
169
177
|
|
|
178
|
+
## Two validation tiers
|
|
179
|
+
|
|
180
|
+
Validation comes in two tiers. This guide covers **Tier 1**, the always-on gate.
|
|
181
|
+
|
|
182
|
+
**Tier 1 — always on (`jay-stack validate`)** is the fast, template-only gate. It reads your `.jay-html` files and
|
|
183
|
+
route scan only — **no build needed** — so it's cheap enough for the hot agent loop and runs on every `validate`.
|
|
184
|
+
Everything described in this guide is Tier 1. For links, Tier 1 (DL#210) resolves every hand-authored `<a href>`
|
|
185
|
+
against the project's static routes + public assets and **errors** on broken internal links (typos, missing
|
|
186
|
+
pages), degenerate `#` / empty placeholder links, and self-links. Links matching a **dynamic** route pattern
|
|
187
|
+
(`/design-log/[...slug]`) are deferred — the concrete slug set isn't known without a build.
|
|
188
|
+
|
|
189
|
+
**Tier 2 — opt-in, against a build (`jay-stack validate --tier-2`)** adds deeper checks that need a build:
|
|
190
|
+
dynamic-slug links, broken links in rendered content, and per-instance meta/SEO. It's documented separately to
|
|
191
|
+
keep this guide focused — see **`validation-tier-2-guide.md`**. Run it pre-deploy / in CI.
|
|
192
|
+
|
|
170
193
|
## Running Validation
|
|
171
194
|
|
|
172
195
|
```bash
|
|
173
|
-
jay-stack validate
|
|
174
|
-
jay-stack validate -v
|
|
175
|
-
jay-stack validate --strict
|
|
196
|
+
jay-stack validate # Tier 1: all pages (+ one-line scorecard totals)
|
|
197
|
+
jay-stack validate -v # add per-page tag coverage + scorecard detail
|
|
198
|
+
jay-stack validate --strict # treat warnings as errors
|
|
199
|
+
jay-stack validate --tier-2 # also run Tier 2 — see validation-tier-2-guide.md
|
|
176
200
|
```
|
|
177
201
|
|
|
178
202
|
Validation runs automatically during `jay-stack build`. Warnings don't block the build; errors do (with `--strict`).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Tier 2 Validation — Deep Checks Against a Build
|
|
2
|
+
|
|
3
|
+
`jay-stack validate` runs **Tier 1**: the fast, template-only gate (links, placeholders, coverage) that needs no
|
|
4
|
+
build. See `validation-guide.md` for Tier 1.
|
|
5
|
+
|
|
6
|
+
**Tier 2** (`jay-stack validate --tier-2`) adds deeper checks that are only possible once a build exists. This
|
|
7
|
+
guide covers Tier 2 end to end.
|
|
8
|
+
|
|
9
|
+
## What Tier 2 is
|
|
10
|
+
|
|
11
|
+
Deep validation against an **existing build's output** (DL#211). It reads
|
|
12
|
+
`build/v<version>/backend/route-manifest.json` plus the per-instance `*.cache.json` slow-render results. It does
|
|
13
|
+
**not** run any render, so it's cheap — but it **requires a prior `jay-stack build`**. If no build is found, Tier 2
|
|
14
|
+
reports a single actionable error (run `jay-stack build` first, or drop `--tier-2`).
|
|
15
|
+
|
|
16
|
+
Run it **pre-deploy / in CI**, not in the hot authoring loop.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
jay-stack validate --tier-2 # validate against the highest build/v* output
|
|
20
|
+
jay-stack validate --t2 # short alias
|
|
21
|
+
jay-stack validate --tier-2 --build-dir <dir> # point at a specific build backend dir or build root
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Tier 2 findings print under their own `📦 Tier 2 — build-output validation (--tier-2)` section, separate from the
|
|
25
|
+
Tier 1 `📦 jay-stack (core)` section.
|
|
26
|
+
|
|
27
|
+
## The three checks
|
|
28
|
+
|
|
29
|
+
All run against the concrete URLs the build actually produced (routes × instances).
|
|
30
|
+
|
|
31
|
+
1. **Deferred dynamic-slug links** (ERROR) — the template links Tier 1 could only defer (because they matched a
|
|
32
|
+
dynamic pattern like `/design-log/[...slug]`) are resolved against the real URL set. A link to a dynamic route
|
|
33
|
+
with a slug the build never generated (e.g. `/design-log/wix/index`) is caught here.
|
|
34
|
+
|
|
35
|
+
2. **Broken links in rendered content** (WARNING) — `<a href>` found _inside_ the slow-rendered ViewState
|
|
36
|
+
(markdown bodies, descriptions) that resolve to a URL the build does not produce. Relative hrefs are resolved
|
|
37
|
+
against the instance's own URL. These are **warnings**, not errors, because content often doubles as repo/GitHub
|
|
38
|
+
docs where repo-relative links like `../pkg/foo.ts` are legitimately correct.
|
|
39
|
+
|
|
40
|
+
3. **Per-instance meta/SEO** (mostly WARNING) — resolves each route's `headMeta` template against each instance's
|
|
41
|
+
slow ViewState and validates the concrete `<title>` / `<meta name="description">`:
|
|
42
|
+
- Empty title/description, a title > 60 chars, or a description > 160 chars → **warning**.
|
|
43
|
+
- A leftover unresolved `{binding}` (a field never produced at any phase) → **error**.
|
|
44
|
+
- Note (DL#189): a field bound to the fast/interactive phase is legitimately empty at slow render, so
|
|
45
|
+
**emptiness is only ever a warning, never an error**.
|
|
46
|
+
|
|
47
|
+
If the build is older than your current source files, a **staleness warning** is emitted ("validated against a
|
|
48
|
+
build from <timestamp>; source has changed since") suggesting a rebuild.
|
|
49
|
+
|
|
50
|
+
## Suppressing Tier 2 findings
|
|
51
|
+
|
|
52
|
+
Per-page, via the same `<script type="application/jay-validations">` block used for Tier 1, under the `jay-stack:`
|
|
53
|
+
key:
|
|
54
|
+
|
|
55
|
+
- `allow-broken-links: true` — suppresses link findings (both template and rendered-content links) for that page.
|
|
56
|
+
- `allow-meta-issues: true` — suppresses meta/SEO findings for that page.
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<script type="application/jay-validations">
|
|
60
|
+
jay-stack:
|
|
61
|
+
allow-broken-links: true
|
|
62
|
+
allow-meta-issues: true
|
|
63
|
+
</script>
|
|
64
|
+
```
|
|
@@ -35,6 +35,7 @@ The developer sets up the project, configures plugins, creates page-level compon
|
|
|
35
35
|
| [component-refs.md](component-refs.md) | Refs, collection refs, element types |
|
|
36
36
|
| [component-data.md](component-data.md) | Immutable data, JSON Patch, patching |
|
|
37
37
|
| [render-results.md](render-results.md) | phaseOutput, RenderPipeline, errors, redirects |
|
|
38
|
+
| [error-pages-guide.md](error-pages-guide.md) | Returning enriched errors (title/code/details) and which error page renders |
|
|
38
39
|
| [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
|
|
39
40
|
| [cli-commands.md](cli-commands.md) | CLI commands: setup, validate, dev, agent-kit |
|
|
40
41
|
| `../references/<plugin>/` | Plugin reference data |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Error Pages (Developer Side)
|
|
2
|
+
|
|
3
|
+
When a page or plugin render returns an error result, Jay renders the matching **error page** (see the
|
|
4
|
+
designer guide for authoring them) with the error data injected as the `error` ViewState. This guide
|
|
5
|
+
covers how to return enriched errors from `page.ts` so they surface well on the rendered page.
|
|
6
|
+
|
|
7
|
+
## Returning errors
|
|
8
|
+
|
|
9
|
+
Return an error result from any render phase to stop rendering and show an error page:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {
|
|
13
|
+
notFound,
|
|
14
|
+
badRequest,
|
|
15
|
+
unauthorized,
|
|
16
|
+
forbidden,
|
|
17
|
+
serverError5xx,
|
|
18
|
+
clientError4xx,
|
|
19
|
+
} from '@jay-framework/fullstack-component';
|
|
20
|
+
|
|
21
|
+
if (!product) return notFound('The product you are looking for may have been removed.');
|
|
22
|
+
|
|
23
|
+
if (!input.query) return badRequest('Query is required');
|
|
24
|
+
|
|
25
|
+
if (!session) return unauthorized('Please log in');
|
|
26
|
+
|
|
27
|
+
if (!canAccess) return forbidden('Access denied');
|
|
28
|
+
|
|
29
|
+
return clientError4xx(429, 'Rate limit exceeded');
|
|
30
|
+
|
|
31
|
+
return serverError5xx(503, 'The inventory service is temporarily unavailable');
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
An **uncaught exception** in a render phase becomes a 500 and renders the `5xx` page — the stack is
|
|
35
|
+
logged, never shown.
|
|
36
|
+
|
|
37
|
+
## Enriching the error page
|
|
38
|
+
|
|
39
|
+
Every error constructor takes an optional options bag after the message: `{ title?, code?, details? }`.
|
|
40
|
+
These map directly onto the `error.*` bindings the error page can render.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
return notFound('The product you are looking for may have been removed.', {
|
|
44
|
+
title: 'Product not found',
|
|
45
|
+
});
|
|
46
|
+
// → error.title = "Product not found", error.message = "The product…"
|
|
47
|
+
|
|
48
|
+
return serverError5xx(504, 'Upstream timed out', {
|
|
49
|
+
title: 'This is taking too long',
|
|
50
|
+
code: 'UPSTREAM_TIMEOUT',
|
|
51
|
+
details: { upstream: 'inventory-service', waitedMs: '10000' },
|
|
52
|
+
});
|
|
53
|
+
// → renders pages/504 if present, else pages/5xx;
|
|
54
|
+
// error.details lists the key/value pairs via forEach="error.details".
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Field | Binding | Notes |
|
|
58
|
+
| --------- | ----------------- | ----------------------------------------------------------- |
|
|
59
|
+
| status | `{error.status}` | The numeric status you pass (`notFound` → 404). |
|
|
60
|
+
| message | `{error.message}` | First positional arg; defaulted per status when omitted. |
|
|
61
|
+
| `title` | `{error.title}` | Short headline; defaulted per status when omitted. |
|
|
62
|
+
| `code` | `{error.code}` | App-specific code; `''` on the page when omitted. |
|
|
63
|
+
| `details` | `error.details` | `Record<string, unknown>` → stringified `{ key, value }[]`. |
|
|
64
|
+
|
|
65
|
+
## Which page renders
|
|
66
|
+
|
|
67
|
+
Resolution is by status, most specific first: exact (`pages/504/`) → family (`pages/5xx/`) → built-in
|
|
68
|
+
plain text. So `serverError5xx(503, …)` renders `pages/503/` if it exists, otherwise `pages/5xx/`.
|
|
69
|
+
`notFound(…)` renders `pages/404/`, or `pages/4xx/` as a family fallback.
|
|
70
|
+
|
|
71
|
+
## RenderPipeline
|
|
72
|
+
|
|
73
|
+
The pipeline factory methods mirror the constructors and accept the same options bag:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const P = RenderPipeline.for<SlowViewState, CarryForward>();
|
|
77
|
+
|
|
78
|
+
return P.try(() => db.getProduct(props.slug))
|
|
79
|
+
.map((product) => product ?? P.notFound('Product not found', { title: 'Product not found' }))
|
|
80
|
+
.toPhaseOutput((product) => ({ viewState: { title: product.name }, carryForward: {} }));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## See also
|
|
84
|
+
|
|
85
|
+
- Full render-result reference (success, redirects, pipeline): `render-results.md`.
|
|
86
|
+
- Authoring the error page markup and bindings: `../designer/error-pages-guide.md`.
|
|
@@ -87,6 +87,22 @@ return clientError4xx(429, 'Rate limit exceeded');
|
|
|
87
87
|
return serverError5xx(500, 'Database connection failed');
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
Each error constructor takes an optional options bag — `{ title?, code?, details? }` — after the
|
|
91
|
+
message. These surface as `error.title` / `error.code` / `error.details` on the error page:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
return notFound('This product may have been removed.', { title: 'Product not found' });
|
|
95
|
+
|
|
96
|
+
return serverError5xx(504, 'Upstream timed out', {
|
|
97
|
+
title: 'This is taking too long',
|
|
98
|
+
details: { upstream: 'inventory-service', waitedMs: '10000' },
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The matching **error page** (`src/pages/404/`, `src/pages/5xx/`, …) renders these fields. See
|
|
103
|
+
[error-pages-guide.md](error-pages-guide.md) for the full field reference and status resolution, and
|
|
104
|
+
`../designer/error-pages-guide.md` for authoring the page markup.
|
|
105
|
+
|
|
90
106
|
## Redirects
|
|
91
107
|
|
|
92
108
|
```typescript
|
|
@@ -53,10 +53,69 @@ interface HeadTag {
|
|
|
53
53
|
// Canonical URL
|
|
54
54
|
{ tag: 'link', attrs: { rel: 'canonical', href: 'https://example.com/products/slug' } }
|
|
55
55
|
|
|
56
|
-
// JSON-LD structured data
|
|
56
|
+
// JSON-LD structured data — prefer the jsonLd() helper (see below) over hand-building this tag
|
|
57
57
|
{ tag: 'script', attrs: { type: 'application/ld+json' }, children: JSON.stringify(structuredData) }
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Structured Data (JSON-LD)
|
|
61
|
+
|
|
62
|
+
JSON-LD tells search engines and AI agents what a page _is_ (a product, article, recipe…). There are three ways to emit it — pick by where the data lives:
|
|
63
|
+
|
|
64
|
+
| Where the data lives | How to emit | Who writes it |
|
|
65
|
+
| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------- |
|
|
66
|
+
| Site-wide identity (org name, logo, social) | `.jay` `site.*` config — auto-emitted as `Organization` + `WebSite` | Developer (config) |
|
|
67
|
+
| Per-page dynamic data (product, article) | `jsonLd(data)` from `page.ts`, returned in `headTags` | Developer (page.ts) |
|
|
68
|
+
| Static, hand-authored for one page | `<script type="application/ld+json">` directly in the jay-html `<head>` | Designer (jay-html) |
|
|
69
|
+
|
|
70
|
+
### 1. Site-wide (`.jay` config)
|
|
71
|
+
|
|
72
|
+
Set site identity once; the production build auto-emits `Organization` + `WebSite` JSON-LD into every page's `<head>`:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
# .jay
|
|
76
|
+
site:
|
|
77
|
+
baseUrl: https://example.com # also drives sitemap.xml
|
|
78
|
+
name: Example Inc # required to emit Organization/WebSite JSON-LD
|
|
79
|
+
logo: /logo.png # relative paths are resolved against baseUrl
|
|
80
|
+
description: What we do
|
|
81
|
+
sameAs:
|
|
82
|
+
- https://twitter.com/example
|
|
83
|
+
- https://github.com/example
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Without `site.name`, no site-level JSON-LD is emitted (validation warns). Site-level JSON-LD is a **production** artifact (like `sitemap.xml`) — the dev server does not inject it.
|
|
87
|
+
|
|
88
|
+
### 2. Per-page (`jsonLd()` helper)
|
|
89
|
+
|
|
90
|
+
Use the `jsonLd()` helper from `@jay-framework/fullstack-component` for dynamic, per-page structured data — it builds the correct `HeadTag` and the framework escapes it breakout-safe:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { jsonLd } from '@jay-framework/fullstack-component';
|
|
94
|
+
|
|
95
|
+
return phaseOutput(viewState, carryForward, {
|
|
96
|
+
headTags: [
|
|
97
|
+
{ tag: 'title', children: `${product.name} | Store` },
|
|
98
|
+
jsonLd({
|
|
99
|
+
'@context': 'https://schema.org',
|
|
100
|
+
'@type': 'Product',
|
|
101
|
+
name: product.name,
|
|
102
|
+
description: product.description,
|
|
103
|
+
offers: { '@type': 'Offer', price: product.price, priceCurrency: 'USD' },
|
|
104
|
+
}),
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Static (jay-html `<head>`)
|
|
110
|
+
|
|
111
|
+
A `<script type="application/ld+json">` written directly in a jay-html `<head>` is **first-class** — no `jay-script="allow"` needed, no inline-script warning. The body is emitted breakout-safe. See the designer SEO guide.
|
|
112
|
+
|
|
113
|
+
### Validation
|
|
114
|
+
|
|
115
|
+
- `jay-stack validate` errors if a template `application/ld+json` block is not valid JSON or is missing `@context`/`@type`.
|
|
116
|
+
- `jay-stack validate` warns if `.jay` `site.name` is absent (no site-level JSON-LD).
|
|
117
|
+
- Never HTML-escape a JSON-LD body yourself — the framework escapes `<`, `>`, `&` as `\uXXXX` so the JSON stays valid and cannot break out of the `<script>`.
|
|
118
|
+
|
|
60
119
|
## Mapping Generic SEO Data
|
|
61
120
|
|
|
62
121
|
If the data source provides a generic structure (array of tags with type/props/children), map it:
|
|
@@ -91,3 +150,53 @@ return phaseOutput(viewState, carryForward, { headTags });
|
|
|
91
150
|
|
|
92
151
|
- Head tags from components inside `forEach` are ignored
|
|
93
152
|
- The framework handles HTML escaping automatically
|
|
153
|
+
|
|
154
|
+
## Agent-Discovery Files (DL#214)
|
|
155
|
+
|
|
156
|
+
A Jay production build emits the conventional files agents and crawlers look for. Most are generated from the route manifest + `.jay` `site` config — you mostly configure `site.*`; one file (`ard.json`) you author.
|
|
157
|
+
|
|
158
|
+
### Generated automatically (override by placing your own file in `public/`)
|
|
159
|
+
|
|
160
|
+
| File | When generated | Source |
|
|
161
|
+
| -------------- | ------------------------------- | ------------------------------------------------------------------------ |
|
|
162
|
+
| `/sitemap.xml` | `site.baseUrl` set | route manifest |
|
|
163
|
+
| `/llms.txt` | `site.name` set | `site.name`/`site.description` + per-page `<title>`/`<meta description>` |
|
|
164
|
+
| `/robots.txt` | always (if you didn't ship one) | default allow-all + `Sitemap:` line when `site.baseUrl` set |
|
|
165
|
+
|
|
166
|
+
`llms.txt` is a markdown index of the site for LLMs: an H1 (`site.name`), a summary (`site.description`), and per-section links with each page's title + description. An author file at `public/llms.txt` / `public/robots.txt` always wins — the build never clobbers it.
|
|
167
|
+
|
|
168
|
+
### `/.well-known/*` — just works in `public/`
|
|
169
|
+
|
|
170
|
+
Drop any `.well-known` file (agent/tool manifests, `security.txt`, etc.) into `public/.well-known/` and it is served at `/.well-known/*` in **both** dev and production. (The dev server opts into serving dotdirs; production already did.)
|
|
171
|
+
|
|
172
|
+
### `/.well-known/ard.json` — you author this
|
|
173
|
+
|
|
174
|
+
Agentic Resource Discovery ([ARD v0.91](https://agenticresourcediscovery.org/spec)) lets agents discover a site's resources/APIs. The framework does **not** generate it — `jay-stack validate` warns when it's absent and validates its shape when present. Author `public/.well-known/ard.json`:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"entries": [
|
|
179
|
+
{
|
|
180
|
+
"identifier": "urn:air:acme:weather:node",
|
|
181
|
+
"displayName": "Weather Data Node",
|
|
182
|
+
"type": "application/mcp-server-card+json",
|
|
183
|
+
"url": "https://api.acme.com/mcp/weather.json",
|
|
184
|
+
"representativeQueries": ["weather in Paris", "forecast for tomorrow"],
|
|
185
|
+
"description": "Live weather data and forecasts",
|
|
186
|
+
"tags": ["weather", "forecast"]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Each entry **requires**: `identifier` (URN `urn:air:<publisher>:<namespace>:<name>`), `displayName`, `type` (an IANA media type), and **exactly one** of `url` or `data`. `representativeQueries` (2–5) is strongly recommended — agents use it to find your resource. `ard.json` is pinned to v0.91 ("Proposal" status — expect the schema to move).
|
|
193
|
+
|
|
194
|
+
**Predecessor alias:** ARD renamed `ai-catalog.json` → `ard.json`. For older clients, also write a copy at `public/.well-known/ai-catalog.json`. `ard.json` is canonical; validation warns if only the alias exists or the two differ.
|
|
195
|
+
|
|
196
|
+
**No agent resources?** If the site exposes nothing agent-callable, suppress the presence nudge instead of authoring an empty file — add to `.jay` (it governs the project-level `ard.json`, so it lives in project config, not a page's `application/jay-validations` script):
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
# .jay
|
|
200
|
+
validations:
|
|
201
|
+
allow-no-ard: true
|
|
202
|
+
```
|
|
@@ -42,6 +42,15 @@ jay-stack validate-plugin -v
|
|
|
42
42
|
- Contract files referenced in `plugin.yaml` exist
|
|
43
43
|
- Component export names are valid strings (not file paths)
|
|
44
44
|
- Action metadata files (`.jay-action`) exist
|
|
45
|
+
- Each `routes[].jayHtml` file **parses and compiles** (same generate stage as `jay-stack validate` — catches invalid template expressions before the dev server)
|
|
46
|
+
|
|
47
|
+
During plugin development, also compile-check **source** templates (paths under `lib/`, not only `dist/`):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
jay-stack validate -p lib/pages/my-route --compile-only
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`--compile-only` skips project rules (broken links, DOM lint, design system) so it is safe inside a plugin package `validate` script alongside `validate-plugin`.
|
|
45
54
|
|
|
46
55
|
### Capability-aware structure
|
|
47
56
|
|