@jay-framework/jay-stack-cli 0.24.6 → 0.24.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-kit-template/INSTRUCTIONS.md +24 -1
- package/agent-kit-template/contracts/GUIDE.md +1 -1
- package/agent-kit-template/contracts/component-contracts.md +42 -12
- package/agent-kit-template/designer/INSTRUCTIONS.md +23 -20
- package/agent-kit-template/designer/cli-commands.md +46 -0
- package/agent-kit-template/designer/contracts-and-plugins.md +30 -1
- package/agent-kit-template/designer/design-system-guide.md +535 -0
- package/agent-kit-template/designer/jay-html-components.md +101 -31
- package/agent-kit-template/designer/jay-html-styling.md +69 -0
- package/agent-kit-template/designer/jay-html-syntax.md +22 -13
- package/agent-kit-template/designer/jay-html-template-syntax.md +3 -3
- package/agent-kit-template/designer/project-structure.md +1 -1
- package/agent-kit-template/designer/routing.md +2 -2
- package/agent-kit-template/designer/validation-guide.md +31 -5
- package/agent-kit-template/developer/INSTRUCTIONS.md +24 -19
- package/agent-kit-template/developer/page-components.md +23 -2
- package/agent-kit-template/developer/project-structure.md +1 -1
- package/agent-kit-template/developer/routing.md +1 -1
- package/agent-kit-template/devops/production-build.md +1 -1
- package/agent-kit-template/plugin/INSTRUCTIONS.md +3 -1
- package/agent-kit-template/plugin/component-structure.md +1 -1
- package/agent-kit-template/plugin/contracts-guide.md +18 -0
- package/agent-kit-template/plugin/design-system-guide.md +173 -0
- package/dist/{index-B9vCxAZ1.js → index-l6QVpvnY.js} +19 -14
- package/dist/index.js +1903 -199
- package/package.json +13 -12
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# Design-System Elements (Materialise, Drift, Sync)
|
|
2
|
+
|
|
3
|
+
A **design-system element** is a component whose template you flatten (copy) into your page and then own
|
|
4
|
+
the copy of. You edit the copy freely; `jay-stack validate` tells you where your copy has drifted from the
|
|
5
|
+
source; `jay-stack sync` re-flattens from the current source while keeping the edits you marked. This is
|
|
6
|
+
Jay's answer to "reuse a shared component but tweak this one instance" — with no runtime cost and no hidden
|
|
7
|
+
composition machinery.
|
|
8
|
+
|
|
9
|
+
**As a designer you both _create_ and _use_ design systems — this is your job, not only a plugin author's.**
|
|
10
|
+
A design-system element can be one you author from scratch, one you copy from a plugin into your project and
|
|
11
|
+
adapt, or one you link straight from a plugin. This guide covers **both sides**: [creating a design-system
|
|
12
|
+
element](#creating-a-design-system-element) (authoring the template + contract) and [using
|
|
13
|
+
one](#using-a-design-system-element-link--flatten) (linking, flattening, editing, syncing).
|
|
14
|
+
|
|
15
|
+
> Replaces the old `<override>` tag. If you have seen `<override ref="…">` in older pages, that mechanism
|
|
16
|
+
> is gone — its job is now done by editing the flattened copy and marking the edited **facet** (see
|
|
17
|
+
> [Marking what you own](#marking-what-you-own-facets)).
|
|
18
|
+
|
|
19
|
+
## The three component models — only one is a design-system element
|
|
20
|
+
|
|
21
|
+
| Model | How it is imported | Template? | Validate / sync? |
|
|
22
|
+
| ------------------------- | -------------------------------------------------- | --------- | ---------------- |
|
|
23
|
+
| **Keyed headless** | `<script … key="cart">`, used via `{cart.field}` | No | No |
|
|
24
|
+
| **Nested (no template)** | `<jay:card>` with an inline body you wrote by hand | No | No |
|
|
25
|
+
| **Design-system element** | `<jay:card>` whose import carries `template="…"` | **Yes** | **Yes** |
|
|
26
|
+
|
|
27
|
+
Only a `<jay:X>` region whose `application/jay-headless` import carries a `template=` attribute is a
|
|
28
|
+
design-system element. That `template=` is the **provenance marker**: it records where the flattened body
|
|
29
|
+
came from, so validate can diff it and sync can re-flatten it. Nothing else — a keyed import, a nested
|
|
30
|
+
region you hand-authored, a plain instance — is ever drift-checked or synced.
|
|
31
|
+
|
|
32
|
+
## Prefer design-system elements
|
|
33
|
+
|
|
34
|
+
Reuse UI through design-system elements, not by hand-authoring the same markup in each page. Three
|
|
35
|
+
preferences — `jay-stack validate` nudges you toward each (warnings, never blocking, each suppressible):
|
|
36
|
+
|
|
37
|
+
1. **Create _and_ consume templates as design-system elements.** When a component renders UI, ship a
|
|
38
|
+
`.jay-html` template for it, and link it from the page with `template=` + `jay-stack sync` rather than
|
|
39
|
+
hand-writing the `<jay:X>` body. (Rules: `COMPONENT-NO-TEMPLATE` on the authoring side,
|
|
40
|
+
`REGION-NOT-LINKED` on the consuming side.)
|
|
41
|
+
2. **When one instance needs extensive restyling or structural change, make _another_ template** for the same
|
|
42
|
+
component — a second design-system variant — instead of piling style/class/structure overrides onto one
|
|
43
|
+
flattened copy. (Rule: `REGION-OVERRIDE-NON-CONTENT`.)
|
|
44
|
+
3. **Hand-author an inline region only for a genuinely one-off usage** — a region used once, where no shared
|
|
45
|
+
template exists or would help.
|
|
46
|
+
|
|
47
|
+
### Two templates vs. a conditional — different axes
|
|
48
|
+
|
|
49
|
+
These solve different problems; do not substitute one for the other:
|
|
50
|
+
|
|
51
|
+
- **Two templates = two design-system elements.** Different _designs_ chosen **at composition time** (a
|
|
52
|
+
compact card vs. a feature card). You pick which template a region links to. This is the axis
|
|
53
|
+
`REGION-OVERRIDE-NON-CONTENT` points you to when an instance's _look or structure_ diverges.
|
|
54
|
+
- **A conditional (`if` / variant) = one element, runtime change.** Same design, a branch driven by
|
|
55
|
+
**runtime state** (logged-in vs. not). Use conditionals for state, not to fork a design.
|
|
56
|
+
|
|
57
|
+
A restyle or net-new layout DOM on one instance is a _second design_ — reach for a second template, not a
|
|
58
|
+
conditional.
|
|
59
|
+
|
|
60
|
+
### What counts as drift worth a new variant
|
|
61
|
+
|
|
62
|
+
`REGION-OVERRIDE-NON-CONTENT` fires only on **non-content** changes to a linked region:
|
|
63
|
+
|
|
64
|
+
- **Content (fine — this is what flattening is for):** editing text, image `src`/`alt`, and enriching text
|
|
65
|
+
with inline content markup (`<strong>`, `<span>`, `<img>`, a `<ul>`/`<li>` list, a `<table>`, headings…).
|
|
66
|
+
- **Non-content (make a new variant):** changing `class` or inline/scoped CSS, or adding net-new layout DOM
|
|
67
|
+
(`<div>`/`<section>` wrappers, custom components) — a different look or structure.
|
|
68
|
+
|
|
69
|
+
## Creating a design-system element
|
|
70
|
+
|
|
71
|
+
Authoring a design-system element is just authoring a **component**: a `.jay-contract` (its data shape,
|
|
72
|
+
props, refs) and a `.jay-html` template (its markup + optional `<style>`). You can start it three ways:
|
|
73
|
+
|
|
74
|
+
- **From scratch** — write the contract and template yourself for a reusable piece of your UI (a card, a
|
|
75
|
+
hero, a callout).
|
|
76
|
+
- **From a plugin, copied in** — copy a plugin's shipped `.jay-html` (and its `.jay-contract`) into your own
|
|
77
|
+
`src/components/…`, then adapt it. Point pages at _your_ copy; it is now project-owned, so you edit the
|
|
78
|
+
source directly — no `override` marks and no upstream `sync`.
|
|
79
|
+
- **From an existing project component** — add a `.jay-html` template to a component that only had a
|
|
80
|
+
contract, so pages can flatten it.
|
|
81
|
+
|
|
82
|
+
### 1. Put it under `src/components/`
|
|
83
|
+
|
|
84
|
+
A project-owned element lives beside your pages:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
src/components/card/
|
|
88
|
+
card.jay-contract # data shape, props, refs
|
|
89
|
+
card.jay-html # the template pages flatten from
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
(A plugin's element lives in its package; you link it by a `template=` path into `node_modules/…`. Same
|
|
93
|
+
mechanics — only the path differs.)
|
|
94
|
+
|
|
95
|
+
### 2. Write the contract
|
|
96
|
+
|
|
97
|
+
The contract is the component's API — the props an instance is configured with and the tags the template
|
|
98
|
+
binds. Keep it to what varies per use. See the [Contract Authoring Guide](../contracts/GUIDE.md).
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
# card.jay-contract
|
|
102
|
+
name: Card
|
|
103
|
+
props:
|
|
104
|
+
- { name: heading, type: string, required: true }
|
|
105
|
+
- { name: body, type: string, required: true }
|
|
106
|
+
tags:
|
|
107
|
+
- { tag: heading, type: data, dataType: string }
|
|
108
|
+
- { tag: body, type: data, dataType: string }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**A pure presentational element can have an (almost) empty contract.** If the element binds no data and
|
|
112
|
+
needs no code — static markup and styles only — the contract still has to exist (it names the element and is
|
|
113
|
+
what the page's `contract=` import resolves), but it needs **nothing but a name**:
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
# hero-banner.jay-contract — a pure design element, no data, no code
|
|
117
|
+
name: HeroBanner
|
|
118
|
+
tags: [] # no data to bind
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Write the template
|
|
122
|
+
|
|
123
|
+
An ordinary `.jay-html`: a `<head>` declaring its contract, a `<body>` of markup binding `{…}` against the
|
|
124
|
+
component's **own** tags, and an optional `<style>` of **plain component CSS**.
|
|
125
|
+
|
|
126
|
+
```html
|
|
127
|
+
<!-- card.jay-html -->
|
|
128
|
+
<html>
|
|
129
|
+
<head>
|
|
130
|
+
<script type="application/jay-data" contract="./card.jay-contract"></script>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<div class="ds-card">
|
|
134
|
+
<h3 class="ds-card__heading">{heading}</h3>
|
|
135
|
+
<p class="ds-card__body" jay-content>{body}</p>
|
|
136
|
+
</div>
|
|
137
|
+
</body>
|
|
138
|
+
<style>
|
|
139
|
+
.ds-card {
|
|
140
|
+
border: 1px solid #e2e8f0;
|
|
141
|
+
padding: 20px;
|
|
142
|
+
}
|
|
143
|
+
.ds-card__heading {
|
|
144
|
+
font-size: 1.15rem;
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
147
|
+
</html>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> **Author plain CSS here, not `@scope`.** In the template you write ordinary class rules
|
|
151
|
+
> (`.ds-card { … }`). When a page flattens the element, `sync` copies this CSS into the page and rewrites it
|
|
152
|
+
> into the canonical `@scope (.<ref>)` / `:scope` form for you — that scoped shape is a _page-side_ concern
|
|
153
|
+
> (see [The shape of a region's CSS](#the-shape-of-a-regions-css)); you don't write it in the template.
|
|
154
|
+
|
|
155
|
+
### 4. Choose the right axis of variation
|
|
156
|
+
|
|
157
|
+
Decide _how_ each instance is allowed to differ, and encode it in the element — this is the core design
|
|
158
|
+
skill:
|
|
159
|
+
|
|
160
|
+
| The instance varies by… | Encode it as… | Example |
|
|
161
|
+
| ---------------------------------------------------- | --------------------------------------------- | -------------------------------- |
|
|
162
|
+
| **data** (same design, different values) | a **contract prop/tag**, bound `{…}` | `{heading}`, `{price}` |
|
|
163
|
+
| **content** (per-page text/media the consumer fills) | a **content slot** — `jay-content` (optional) | a body paragraph, a hero `<img>` |
|
|
164
|
+
| **runtime state** (same design, a branch) | a **conditional** (`if` / variant) | logged-in vs. not |
|
|
165
|
+
| **design / structure** (a different look) | a **separate template** (a second element) | compact card vs. feature card |
|
|
166
|
+
|
|
167
|
+
Prefer props for anything that is really just data. Use `jay-content` to pre-open the content a page is meant
|
|
168
|
+
to fill (optional — it spares the consumer an `override`; see [Content
|
|
169
|
+
slots](#content-slots--jay-content-an-optional-helper)). Fork a **second template**, not a conditional, when
|
|
170
|
+
the _look or structure_ differs — see [Two templates vs. a conditional](#two-templates-vs-a-conditional--different-axes).
|
|
171
|
+
|
|
172
|
+
### 5. Compose other elements inside it
|
|
173
|
+
|
|
174
|
+
A template may nest other design-system elements (`<jay:button>`). Declare their contract import in the
|
|
175
|
+
template `<head>`; when a page flattens this element, the nested regions flatten **transitively** (the page
|
|
176
|
+
must also carry a `template=` import for each nested element so it resolves). See
|
|
177
|
+
[jay-html-components.md](jay-html-components.md).
|
|
178
|
+
|
|
179
|
+
Once the element exists, pages consume it exactly as below.
|
|
180
|
+
|
|
181
|
+
## Using a design-system element (link + flatten)
|
|
182
|
+
|
|
183
|
+
### 1. Declare the import with `template=`
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<head>
|
|
187
|
+
<script
|
|
188
|
+
type="application/jay-headless"
|
|
189
|
+
contract="./components/card/card.jay-contract"
|
|
190
|
+
template="./components/card/card.jay-html"
|
|
191
|
+
></script>
|
|
192
|
+
</head>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- `contract=` — the component's contract (data shape, refs, props), as for any headless import.
|
|
196
|
+
- `template=` — the source `.jay-html` to flatten from. Resolved **relative to the page directory**.
|
|
197
|
+
|
|
198
|
+
A `template=` that does not resolve is a **hard error**, not a warning — fix the path or remove the
|
|
199
|
+
attribute.
|
|
200
|
+
|
|
201
|
+
### 2. Place the region and flatten it
|
|
202
|
+
|
|
203
|
+
Write the tag, then let `sync` fill its body from the source template:
|
|
204
|
+
|
|
205
|
+
```html
|
|
206
|
+
<body>
|
|
207
|
+
<jay:card ref="promo" heading="{title}"></jay:card>
|
|
208
|
+
</body>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
jay-stack sync
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
After sync the region carries a flattened copy of the source body, plus the component's CSS copied into
|
|
216
|
+
the page's `<style>`, `@scope`-wrapped by the region's `ref`:
|
|
217
|
+
|
|
218
|
+
```html
|
|
219
|
+
<jay:card ref="promo" heading="{title}">
|
|
220
|
+
<div class="card promo">
|
|
221
|
+
<h3 class="card-heading">{heading}</h3>
|
|
222
|
+
<p class="card-body">Default body</p>
|
|
223
|
+
</div>
|
|
224
|
+
</jay:card>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```css
|
|
228
|
+
@scope (.promo) {
|
|
229
|
+
:scope {
|
|
230
|
+
border: 1px solid #ccc;
|
|
231
|
+
padding: 16px;
|
|
232
|
+
}
|
|
233
|
+
.card-heading {
|
|
234
|
+
color: black;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Note two things sync did to the CSS — both required for the styles to actually apply. **Write region CSS in
|
|
240
|
+
this shape yourself**; the rules are below in [The shape of a region's CSS](#the-shape-of-a-regions-css).
|
|
241
|
+
|
|
242
|
+
> A bare `<jay:X>` with **no** flattened body is a hard error at build time — flatten it with `jay-stack
|
|
243
|
+
sync` (first-fill is just the no-edits case of sync; there is no separate `add` command).
|
|
244
|
+
|
|
245
|
+
### 3. Edit the copy freely
|
|
246
|
+
|
|
247
|
+
The flattened body is yours. Rewrite text, change classes, add/remove children, edit the scoped CSS. Bind
|
|
248
|
+
`{…}` expressions against the component's own contract — inside `<jay:card>`, `{heading}` is the card's
|
|
249
|
+
`heading` prop, not the page's. What you may change without prompting a "make a new variant" nudge is the
|
|
250
|
+
[content vs. non-content](#what-counts-as-drift-worth-a-new-variant) distinction above.
|
|
251
|
+
|
|
252
|
+
### 4. Two variants of one component on a page — `as=`
|
|
253
|
+
|
|
254
|
+
A `<jay:X>` region tag is derived from the imported contract's name, so importing one contract twice would
|
|
255
|
+
make **both** imports back the same `<jay:card>` tag — an ambiguous collision (`REGION-TAG-COLLISION`, a
|
|
256
|
+
hard error). To flatten **two [design variants](#two-templates-vs-a-conditional--different-axes) of one
|
|
257
|
+
contract** side by side (e.g. a default card and a feature card — same data shape, different design), give
|
|
258
|
+
the second import its own region tag with `as=`:
|
|
259
|
+
|
|
260
|
+
```html
|
|
261
|
+
<head>
|
|
262
|
+
<!-- default card → <jay:card> -->
|
|
263
|
+
<script
|
|
264
|
+
type="application/jay-headless"
|
|
265
|
+
contract="./components/card/card.jay-contract"
|
|
266
|
+
template="./components/card/card.jay-html"
|
|
267
|
+
></script>
|
|
268
|
+
<!-- feature card (same contract, different template) → <jay:feature-card> -->
|
|
269
|
+
<script
|
|
270
|
+
type="application/jay-headless"
|
|
271
|
+
contract="./components/card/card.jay-contract"
|
|
272
|
+
template="./components/card/card.feature.jay-html"
|
|
273
|
+
as="feature-card"
|
|
274
|
+
></script>
|
|
275
|
+
</head>
|
|
276
|
+
|
|
277
|
+
<body>
|
|
278
|
+
<jay:card ref="starter" heading="Starter"></jay:card>
|
|
279
|
+
<jay:feature-card ref="pro" heading="Pro"></jay:feature-card>
|
|
280
|
+
</body>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- `as=` must be a **kebab-case region tag** (`[a-z][a-z0-9-]*`), e.g. `as="feature-card"` → `<jay:feature-card>`.
|
|
284
|
+
- Both imports share the same contract, so both regions bind the **same props/refs** — only the design
|
|
285
|
+
(template + CSS) differs. Each region flattens and drifts independently from its own `template=`.
|
|
286
|
+
- `as=` composes at every nesting level: a template may alias the nested regions it uses (two
|
|
287
|
+
`<jay:button>`s as `<jay:main-cta>` / `<jay:secondary-cta>`), as long as the page carries a matching
|
|
288
|
+
aliased import for each.
|
|
289
|
+
- Write a second variant only when the difference is [non-content](#what-counts-as-drift-worth-a-new-variant)
|
|
290
|
+
(class / CSS / structure). A difference that is just **runtime state** (same design, a branch) is a
|
|
291
|
+
conditional inside **one** element, not a second import.
|
|
292
|
+
|
|
293
|
+
## The shape of a region's CSS
|
|
294
|
+
|
|
295
|
+
A region's CSS is the component's own CSS, copied into the page `<style>` and rewritten into a canonical
|
|
296
|
+
form. `sync` produces this form and `validate` enforces it — but when you **hand-write or edit** a region's
|
|
297
|
+
CSS, write it this way directly:
|
|
298
|
+
|
|
299
|
+
### 1. Wrap the component's rules in `@scope (.<ref>)`
|
|
300
|
+
|
|
301
|
+
All of a region's rules live inside one `@scope` block keyed by the region's `ref`:
|
|
302
|
+
|
|
303
|
+
```css
|
|
304
|
+
@scope (.promo) {
|
|
305
|
+
/* …the card's rules… */
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
This isolates the component's styles to that region — rules inside never leak out, and page rules outside
|
|
310
|
+
never bleed in.
|
|
311
|
+
|
|
312
|
+
### 2. The `ref` is a real class on the region root (scope-anchor)
|
|
313
|
+
|
|
314
|
+
A jay `ref` is not emitted to the DOM, so `@scope (.promo)` would have nothing to match. The region root
|
|
315
|
+
therefore carries the ref **as an actual class** — `<div class="card promo">` above. Keep that class on the
|
|
316
|
+
root when you edit markup; it is what anchors the scope. (validate's drift check ignores this synthetic
|
|
317
|
+
class, so it never shows up as drift.)
|
|
318
|
+
|
|
319
|
+
### 3. The component's own root rule targets `:scope`, not its block class
|
|
320
|
+
|
|
321
|
+
This is the one that trips people up. **Inside `@scope (.<ref>) { … }`, a scoped selector matches
|
|
322
|
+
_descendants_ of the scope root only — the scope root element itself is reachable solely through `:scope`.**
|
|
323
|
+
So a rule for the region's own root element must be written as `:scope`, _not_ as the root's class:
|
|
324
|
+
|
|
325
|
+
```css
|
|
326
|
+
@scope (.promo) {
|
|
327
|
+
:scope {
|
|
328
|
+
border: 1px solid #ccc;
|
|
329
|
+
} /* ✅ styles the region root (<div class="card promo">) */
|
|
330
|
+
.card {
|
|
331
|
+
border: 1px solid #ccc;
|
|
332
|
+
} /* ❌ never matches — .card IS the scope root, not a descendant */
|
|
333
|
+
.card-heading {
|
|
334
|
+
color: black;
|
|
335
|
+
} /* ✅ descendant — plain class selector is correct */
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Rule of thumb: the component's **root block class** → `:scope`; keep compounds and descendants on that
|
|
340
|
+
class (`.card.active` → `:scope.active`, `.card .card-heading` → `:scope .card-heading`), and leave every
|
|
341
|
+
**descendant/element** selector as its ordinary class.
|
|
342
|
+
|
|
343
|
+
### 4. Instances of the same component coalesce into one block
|
|
344
|
+
|
|
345
|
+
When a page holds several regions flattened from the _same_ template, they share one `@scope` block with a
|
|
346
|
+
selector list — never one duplicated block per `ref`:
|
|
347
|
+
|
|
348
|
+
```css
|
|
349
|
+
@scope (.cardStarter, .cardPro) {
|
|
350
|
+
:scope {
|
|
351
|
+
border: 1px solid #ccc;
|
|
352
|
+
}
|
|
353
|
+
.card-heading {
|
|
354
|
+
color: black;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Regions from _different_ templates stay in separate blocks. If you add a second instance by hand, fold its
|
|
360
|
+
ref into the existing block's selector list rather than copying the block.
|
|
361
|
+
|
|
362
|
+
## Drift: what `jay-stack validate` tells you
|
|
363
|
+
|
|
364
|
+
Because the copy has a known source (its `template=`), `jay-stack validate` diffs the two and reports each
|
|
365
|
+
place your copy differs — at **facet** granularity (one attribute, one style declaration, a subtree, one
|
|
366
|
+
CSS rule or declaration), not "this node changed":
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
<jay:card> region differs from source template "./components/card/card.jay-html":
|
|
370
|
+
<h3> children changed ("{heading}" → "On sale now").
|
|
371
|
+
To keep the page's version, mark the node override="children";
|
|
372
|
+
to discard it and re-flatten from source, run `jay-stack sync`.
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Every drift is a **warning**, and the page still builds. A warning is a decision point, not a failure:
|
|
376
|
+
|
|
377
|
+
- **Keep your edit** → mark the facet (below). It will no longer be reported, and `sync` will preserve it.
|
|
378
|
+
- **Discard your edit** → run `jay-stack sync` to re-flatten that facet from source.
|
|
379
|
+
|
|
380
|
+
An unedited copy validates clean. A `template=` that cannot be read, and a template that (transitively)
|
|
381
|
+
includes itself, are **hard errors**.
|
|
382
|
+
|
|
383
|
+
## Marking what you own (facets)
|
|
384
|
+
|
|
385
|
+
To keep an edit, mark the exact facet you own. Everything you do not mark still reconciles with source, so
|
|
386
|
+
you get the source's future fixes for free on the parts you did not touch.
|
|
387
|
+
|
|
388
|
+
### Markup facets — the `override` attribute
|
|
389
|
+
|
|
390
|
+
| You own… | Mark it | Effect |
|
|
391
|
+
| ------------------------------- | --------------------------- | ------------------------------------------------------ |
|
|
392
|
+
| one attribute | `override="class"` | your `class` is kept; other attributes reconcile |
|
|
393
|
+
| one inline-style declaration | `override="style.color"` | your `color` is kept; other style props reconcile |
|
|
394
|
+
| this element's children/subtree | `override="children"` | your subtree is kept; the element's attrs reconcile |
|
|
395
|
+
| several facets at once | `override="class children"` | space- **or** comma-separated list; each facet is kept |
|
|
396
|
+
| the whole node | `override` (bare) or `*` | the entire node is kept verbatim |
|
|
397
|
+
|
|
398
|
+
`override` is a **facet list**: list the facets you own, separated by spaces or commas (`override="class, style.color"`). A bare `override` (or `override="*"`) is the shorthand for "the whole node is mine." Anything not listed still reconciles with source.
|
|
399
|
+
|
|
400
|
+
```html
|
|
401
|
+
<jay:card ref="promo" heading="{item.title}">
|
|
402
|
+
<div class="card featured" override="class">
|
|
403
|
+
<!-- class is page-owned; the rest of this div still reconciles -->
|
|
404
|
+
<h3 override="children">Half price this week</h3>
|
|
405
|
+
<p class="card-body">Default body</p>
|
|
406
|
+
</div>
|
|
407
|
+
</jay:card>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
- **Removing a child:** delete it and mark the parent `override="children"` — otherwise `sync` restores it.
|
|
411
|
+
- **Attribute / style tweak:** edit the value and mark that facet (`override="href"`,
|
|
412
|
+
`override="style.color"`), leaving siblings to reconcile.
|
|
413
|
+
|
|
414
|
+
### CSS facets — the `jay:override` pragma
|
|
415
|
+
|
|
416
|
+
A comment has no element to hang an attribute on, so scoped CSS uses a pragma placed immediately before (or
|
|
417
|
+
inside) the rule:
|
|
418
|
+
|
|
419
|
+
```css
|
|
420
|
+
@scope (.promo) {
|
|
421
|
+
/* jay:override: color */
|
|
422
|
+
.card-heading {
|
|
423
|
+
color: red; /* kept across sync */
|
|
424
|
+
font-weight: bold; /* still reconciles with source */
|
|
425
|
+
}
|
|
426
|
+
/* jay:override */
|
|
427
|
+
.card-badge {
|
|
428
|
+
/* the whole rule is page-owned */
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- `/* jay:override: <prop> */` — the page owns that one declaration.
|
|
434
|
+
- `/* jay:override */` — the page owns the whole rule.
|
|
435
|
+
|
|
436
|
+
### Content slots — `jay-content` (an optional helper)
|
|
437
|
+
|
|
438
|
+
`override` lives on the **page** and is per-instance. `jay-content` is its mirror on the **template** side —
|
|
439
|
+
and because you [author templates too](#creating-a-design-system-element), it is a tool you reach for when
|
|
440
|
+
_creating_ an element, not something only plugin authors use. Put it on a template node to pre-declare a
|
|
441
|
+
**content slot**: "this part is meant to be filled per page." Edits to a marked facet are then expected by
|
|
442
|
+
design — `validate` never reports them as drift, and `sync` keeps the consumer's version — **without** any
|
|
443
|
+
page-side `override`.
|
|
444
|
+
|
|
445
|
+
```html
|
|
446
|
+
<!-- in a template you author or own: -->
|
|
447
|
+
<p jay-content>{body}</p>
|
|
448
|
+
<img jay-content="src alt" src="placeholder.png" alt="" />
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
```html
|
|
452
|
+
<!-- in each flattened page, just edit — no override needed: -->
|
|
453
|
+
<p>Everything your team needs, in one place.</p>
|
|
454
|
+
<img src="/hero.png" alt="Our team at work" />
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**`jay-content` is optional, not required.** It is purely a convenience that removes the need to mark
|
|
458
|
+
`override` on the slot. Unlike slot/children mechanisms in other frameworks, you do **not** have to annotate
|
|
459
|
+
every variable spot — an unmarked content edit still works; it just shows up as drift until you either mark
|
|
460
|
+
it (`override` on the page, or `jay-content` on the template) or `sync` it away. Reach for `jay-content` when
|
|
461
|
+
a node is _meant_ to vary across pages and you'd rather not repeat an `override` on every instance.
|
|
462
|
+
|
|
463
|
+
It is a **template-side** marker — it lives on the source template and is stripped from the flattened page
|
|
464
|
+
(so it is stated once, and resilient to template edits). It uses the **same facet-list grammar** as
|
|
465
|
+
`override` (`children`, `style.<prop>`, attribute names, `*` — see
|
|
466
|
+
[_Marking what you own_](#marking-what-you-own-facets)), with one difference: a **bare** `jay-content` means
|
|
467
|
+
**children** (the common text slot), whereas a bare `override` means the whole node.
|
|
468
|
+
|
|
469
|
+
## Upgrading: `jay-stack sync`
|
|
470
|
+
|
|
471
|
+
When the source template changes (a component upgrade, a design fix), re-flatten:
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
jay-stack sync # every design-system region in the project
|
|
475
|
+
jay-stack sync src/pages/home.jay-html # one page
|
|
476
|
+
jay-stack sync --all # explicit "all pages"
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Sync is **re-flatten, not merge**: it overwrites everything from the current source **except** the facets
|
|
480
|
+
you marked `override`, which it preserves verbatim. There is no merge base, no hash, no conflict prompt —
|
|
481
|
+
your region is by definition equal to its source except at your marked facets, so sync is deterministic
|
|
482
|
+
overwrite-with-holes. It can never silently combine two edited versions into wrong markup. A synced region
|
|
483
|
+
validates clean.
|
|
484
|
+
|
|
485
|
+
CSS reconciles the same way as markup — drift is overwritten, marked facets are kept:
|
|
486
|
+
|
|
487
|
+
- A region block **equal** to the canonical form (or missing) is (re-)emitted as the canonical
|
|
488
|
+
`@scope (.<ref>)` block, coalesced with its same-template siblings.
|
|
489
|
+
- A block that **diverges but carries a `/* jay:override */` pragma** is preserved verbatim — your pragma is
|
|
490
|
+
never rewritten.
|
|
491
|
+
- A block that **diverges without any pragma** is unmarked CSS drift: `sync` overwrites it back to the
|
|
492
|
+
canonical form, exactly as it re-flattens unmarked markup. (This is what lets `sync` _migrate_ a page to a
|
|
493
|
+
new canonical form — e.g. an older `.card { … }` root rule is rewritten to `:scope { … }`.)
|
|
494
|
+
|
|
495
|
+
So after one `sync` a page's CSS is in canonical form, and a `validate`-clean page is one `sync` leaves
|
|
496
|
+
unchanged. To keep a hand-edit through `sync`, mark it with a `jay:override` pragma.
|
|
497
|
+
|
|
498
|
+
## Suppressing the preference warnings
|
|
499
|
+
|
|
500
|
+
Each "prefer design-system elements" warning is suppressible for the legitimate case:
|
|
501
|
+
|
|
502
|
+
| Rule | Suppress |
|
|
503
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
504
|
+
| `REGION-NOT-LINKED` | `jay-validations="REGION-NOT-LINKED"` on the `application/jay-headless` import (per region type), or list the contract under `allow-inline-region` (below) |
|
|
505
|
+
| `REGION-OVERRIDE-NON-CONTENT` | `jay-validations="REGION-OVERRIDE-NON-CONTENT"` on the import |
|
|
506
|
+
| `COMPONENT-NO-TEMPLATE` | `allow-no-template: [Contract]` (a data-only component has no `.jay-html` to host an attribute) |
|
|
507
|
+
| `NO-DESIGN-SYSTEM` | `allow-no-design-system: true` |
|
|
508
|
+
|
|
509
|
+
Per-region-type suppression lives on the import; put multiple rules in one attribute:
|
|
510
|
+
|
|
511
|
+
```html
|
|
512
|
+
<script
|
|
513
|
+
type="application/jay-headless"
|
|
514
|
+
contract="./components/card/card.jay-contract"
|
|
515
|
+
jay-validations="REGION-NOT-LINKED REGION-OVERRIDE-NON-CONTENT"
|
|
516
|
+
></script>
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Project-wide / list suppression lives in the page's validations script:
|
|
520
|
+
|
|
521
|
+
```html
|
|
522
|
+
<script type="application/jay-validations">
|
|
523
|
+
jay-stack:
|
|
524
|
+
allow-inline-region: [Card, HeroBanner] # contract names — allow hand-authored regions
|
|
525
|
+
allow-no-template: [MetricsProvider] # data-only components with no UI
|
|
526
|
+
allow-no-design-system: true # project intentionally shares no design system
|
|
527
|
+
</script>
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## Why copy instead of reference?
|
|
531
|
+
|
|
532
|
+
Flattening removes the composition "seam": the region compiles through the ordinary headless-instance path,
|
|
533
|
+
so there is **no runtime crossing machinery** and no per-component ref-scope rules. The cost is that page
|
|
534
|
+
files grow and a copy duplicates its source — which is exactly what `validate` (drift) and `sync`
|
|
535
|
+
(re-flatten) exist to manage. Design edits stay in the page; the source stays reusable.
|