@jay-framework/jay-stack-cli 0.24.6 → 0.25.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.
Files changed (26) hide show
  1. package/agent-kit-template/INSTRUCTIONS.md +24 -1
  2. package/agent-kit-template/contracts/GUIDE.md +1 -1
  3. package/agent-kit-template/contracts/component-contracts.md +42 -12
  4. package/agent-kit-template/designer/INSTRUCTIONS.md +23 -20
  5. package/agent-kit-template/designer/cli-commands.md +46 -0
  6. package/agent-kit-template/designer/contracts-and-plugins.md +30 -1
  7. package/agent-kit-template/designer/design-system-guide.md +547 -0
  8. package/agent-kit-template/designer/jay-html-components.md +101 -31
  9. package/agent-kit-template/designer/jay-html-styling.md +70 -0
  10. package/agent-kit-template/designer/jay-html-syntax.md +22 -13
  11. package/agent-kit-template/designer/jay-html-template-syntax.md +3 -3
  12. package/agent-kit-template/designer/project-structure.md +1 -1
  13. package/agent-kit-template/designer/routing.md +2 -2
  14. package/agent-kit-template/designer/validation-guide.md +65 -6
  15. package/agent-kit-template/developer/INSTRUCTIONS.md +24 -19
  16. package/agent-kit-template/developer/page-components.md +23 -2
  17. package/agent-kit-template/developer/project-structure.md +1 -1
  18. package/agent-kit-template/developer/routing.md +1 -1
  19. package/agent-kit-template/devops/production-build.md +1 -1
  20. package/agent-kit-template/plugin/INSTRUCTIONS.md +3 -1
  21. package/agent-kit-template/plugin/component-structure.md +1 -1
  22. package/agent-kit-template/plugin/contracts-guide.md +18 -0
  23. package/agent-kit-template/plugin/design-system-guide.md +173 -0
  24. package/dist/{index-B9vCxAZ1.js → index-l6QVpvnY.js} +19 -14
  25. package/dist/index.js +2016 -212
  26. package/package.json +13 -12
@@ -0,0 +1,547 @@
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 these rules into the page verbatim and
152
+ > wraps them in a canonical `@scope (.<ref>)` block for you — that scoped wrapper 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">
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
+ .card {
230
+ border: 1px solid #ccc;
231
+ padding: 16px;
232
+ }
233
+ .card-heading {
234
+ color: black;
235
+ }
236
+ }
237
+ ```
238
+
239
+ The flattened body is the template body **verbatim** — no synthetic class on the root — and the CSS is the
240
+ component's own plain rules wrapped in one `@scope (.<ref>)` block. **Write region CSS in this shape
241
+ yourself**; the rules are below in [The shape of a region's CSS](#the-shape-of-a-regions-css).
242
+
243
+ > A bare `<jay:X>` with **no** flattened body is a hard error at build time — flatten it with `jay-stack
244
+ sync` (first-fill is just the no-edits case of sync; there is no separate `add` command).
245
+
246
+ ### 3. Edit the copy freely
247
+
248
+ The flattened body is yours. Rewrite text, change classes, add/remove children, edit the scoped CSS. Bind
249
+ `{…}` expressions against the component's own contract — inside `<jay:card>`, `{heading}` is the card's
250
+ `heading` prop, not the page's. What you may change without prompting a "make a new variant" nudge is the
251
+ [content vs. non-content](#what-counts-as-drift-worth-a-new-variant) distinction above.
252
+
253
+ ### 4. Two variants of one component on a page — `as=`
254
+
255
+ A `<jay:X>` region tag is derived from the imported contract's name, so importing one contract twice would
256
+ make **both** imports back the same `<jay:card>` tag — an ambiguous collision (`REGION-TAG-COLLISION`, a
257
+ hard error). To flatten **two [design variants](#two-templates-vs-a-conditional--different-axes) of one
258
+ contract** side by side (e.g. a default card and a feature card — same data shape, different design), give
259
+ the second import its own region tag with `as=`:
260
+
261
+ ```html
262
+ <head>
263
+ <!-- default card → <jay:card> -->
264
+ <script
265
+ type="application/jay-headless"
266
+ contract="./components/card/card.jay-contract"
267
+ template="./components/card/card.jay-html"
268
+ ></script>
269
+ <!-- feature card (same contract, different template) → <jay:feature-card> -->
270
+ <script
271
+ type="application/jay-headless"
272
+ contract="./components/card/card.jay-contract"
273
+ template="./components/card/card.feature.jay-html"
274
+ as="feature-card"
275
+ ></script>
276
+ </head>
277
+
278
+ <body>
279
+ <jay:card ref="starter" heading="Starter"></jay:card>
280
+ <jay:feature-card ref="pro" heading="Pro"></jay:feature-card>
281
+ </body>
282
+ ```
283
+
284
+ - `as=` must be a **kebab-case region tag** (`[a-z][a-z0-9-]*`), e.g. `as="feature-card"` → `<jay:feature-card>`.
285
+ - Both imports share the same contract, so both regions bind the **same props/refs** — only the design
286
+ (template + CSS) differs. Each region flattens and drifts independently from its own `template=`.
287
+ - `as=` composes at every nesting level: a template may alias the nested regions it uses (two
288
+ `<jay:button>`s as `<jay:main-cta>` / `<jay:secondary-cta>`), as long as the page carries a matching
289
+ aliased import for each.
290
+ - Write a second variant only when the difference is [non-content](#what-counts-as-drift-worth-a-new-variant)
291
+ (class / CSS / structure). A difference that is just **runtime state** (same design, a branch) is a
292
+ conditional inside **one** element, not a second import.
293
+
294
+ ## The shape of a region's CSS
295
+
296
+ A region's CSS is the component's own CSS, copied into the page `<style>` and wrapped in one `@scope` block.
297
+ `sync` produces this form and `validate` enforces it — but when you **hand-write or edit** a region's CSS,
298
+ write it this way directly:
299
+
300
+ ### 1. Wrap the component's rules in `@scope (.<ref>)`
301
+
302
+ All of a region's rules live inside one `@scope` block keyed by the region's `ref`:
303
+
304
+ ```css
305
+ @scope (.promo) {
306
+ /* …the card's rules, as plain class selectors… */
307
+ }
308
+ ```
309
+
310
+ This isolates the component's styles to that region — rules inside never leak out, and page rules outside
311
+ never bleed in.
312
+
313
+ ### 2. Write plain class selectors — the ref is _not_ a class on your markup
314
+
315
+ A jay `ref` is not emitted to the DOM, so `@scope (.promo)` would have nothing to match. The compiler
316
+ therefore synthesizes a `<div class="promo" style="display: contents">` **scope-anchor wrapper** around the
317
+ region body at build time (DL#206). It is layout-transparent (`display:contents` generates no box) and you
318
+ **never write it, see it in source, or style it** — it exists only so `.promo` has a real element to root at.
319
+
320
+ Because the wrapper is the scope root, your real roots are ordinary **descendants** of it. So every rule —
321
+ including the component's own root element — is a **plain class selector**, exactly as authored in the
322
+ template. Keep the template's classes on your markup unchanged; add **no** ref class:
323
+
324
+ ```css
325
+ @scope (.promo) {
326
+ .card {
327
+ border: 1px solid #ccc;
328
+ } /* ✅ the region root — a plain class selector, matched as a descendant of the wrapper */
329
+ .card-heading {
330
+ color: black;
331
+ } /* ✅ descendant — also a plain class selector */
332
+ }
333
+ ```
334
+
335
+ > **No `:scope` rewrite.** In the old model the root rule was rewritten to `:scope` and the ref was stamped
336
+ > onto the root as a class. DL#206 removed both: page region CSS is now byte-for-byte the template's plain
337
+ > rules, just wrapped in `@scope (.<ref>)`. `:scope` would now refer to the invisible wrapper — don't use it.
338
+
339
+ ### 3. Instances of the same component coalesce into one block
340
+
341
+ When a page holds several regions flattened from the _same_ template, they share one `@scope` block with a
342
+ selector list — never one duplicated block per `ref`:
343
+
344
+ ```css
345
+ @scope (.cardStarter, .cardPro) {
346
+ .card {
347
+ border: 1px solid #ccc;
348
+ }
349
+ .card-heading {
350
+ color: black;
351
+ }
352
+ }
353
+ ```
354
+
355
+ Regions from _different_ templates stay in separate blocks. If you add a second instance by hand, fold its
356
+ ref into the existing block's selector list rather than copying the block.
357
+
358
+ ### 4. A CSS-shipping region must have a `ref` (error)
359
+
360
+ The `@scope (.<ref>)` anchor is derived from the region's `ref=`. A region whose `template=` ships CSS but
361
+ whose `<jay:X>` tag has **no `ref`** has nothing to scope to — the materialiser cannot wrap the CSS and
362
+ `jay-stack sync` silently drops it. `validate` catches this as a **hard error** (`REGION-CSS-NO-REF`, DL#209):
363
+
364
+ ```
365
+ <jay:card> flattens template="./components/card/card.jay-html" which ships CSS, but the region has no ref=
366
+ — so its CSS cannot be scoped and `jay-stack sync` will silently drop it.
367
+ → Add a ref= to the <jay:card> so sync can scope its CSS as an @scope (.<ref>) block and preserve it.
368
+ ```
369
+
370
+ Fix it by adding a `ref` — there is no suppression, because the alternative (keeping the CSS unscoped) would
371
+ leak the region's styles across the whole page.
372
+
373
+ ## Drift: what `jay-stack validate` tells you
374
+
375
+ Because the copy has a known source (its `template=`), `jay-stack validate` diffs the two and reports each
376
+ place your copy differs — at **facet** granularity (one attribute, one style declaration, a subtree, one
377
+ CSS rule or declaration), not "this node changed":
378
+
379
+ ```
380
+ <jay:card> region differs from source template "./components/card/card.jay-html":
381
+ <h3> children changed ("{heading}" → "On sale now").
382
+ To keep the page's version, mark the node override="children";
383
+ to discard it and re-flatten from source, run `jay-stack sync`.
384
+ ```
385
+
386
+ Every drift is a **warning**, and the page still builds. A warning is a decision point, not a failure:
387
+
388
+ - **Keep your edit** → mark the facet (below). It will no longer be reported, and `sync` will preserve it.
389
+ - **Discard your edit** → run `jay-stack sync` to re-flatten that facet from source.
390
+
391
+ An unedited copy validates clean. A `template=` that cannot be read, and a template that (transitively)
392
+ includes itself, are **hard errors**.
393
+
394
+ ## Marking what you own (facets)
395
+
396
+ To keep an edit, mark the exact facet you own. Everything you do not mark still reconciles with source, so
397
+ you get the source's future fixes for free on the parts you did not touch.
398
+
399
+ ### Markup facets — the `override` attribute
400
+
401
+ | You own… | Mark it | Effect |
402
+ | ------------------------------- | --------------------------- | ------------------------------------------------------ |
403
+ | one attribute | `override="class"` | your `class` is kept; other attributes reconcile |
404
+ | one inline-style declaration | `override="style.color"` | your `color` is kept; other style props reconcile |
405
+ | this element's children/subtree | `override="children"` | your subtree is kept; the element's attrs reconcile |
406
+ | several facets at once | `override="class children"` | space- **or** comma-separated list; each facet is kept |
407
+ | the whole node | `override` (bare) or `*` | the entire node is kept verbatim |
408
+
409
+ `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.
410
+
411
+ ```html
412
+ <jay:card ref="promo" heading="{item.title}">
413
+ <div class="card featured" override="class">
414
+ <!-- class is page-owned; the rest of this div still reconciles -->
415
+ <h3 override="children">Half price this week</h3>
416
+ <p class="card-body">Default body</p>
417
+ </div>
418
+ </jay:card>
419
+ ```
420
+
421
+ - **Removing a child:** delete it and mark the parent `override="children"` — otherwise `sync` restores it.
422
+ - **Attribute / style tweak:** edit the value and mark that facet (`override="href"`,
423
+ `override="style.color"`), leaving siblings to reconcile.
424
+
425
+ ### CSS facets — the `jay:override` pragma
426
+
427
+ A comment has no element to hang an attribute on, so scoped CSS uses a pragma placed immediately before (or
428
+ inside) the rule:
429
+
430
+ ```css
431
+ @scope (.promo) {
432
+ /* jay:override: color */
433
+ .card-heading {
434
+ color: red; /* kept across sync */
435
+ font-weight: bold; /* still reconciles with source */
436
+ }
437
+ /* jay:override */
438
+ .card-badge {
439
+ /* the whole rule is page-owned */
440
+ }
441
+ }
442
+ ```
443
+
444
+ - `/* jay:override: <prop> */` — the page owns that one declaration.
445
+ - `/* jay:override */` — the page owns the whole rule.
446
+
447
+ ### Content slots — `jay-content` (an optional helper)
448
+
449
+ `override` lives on the **page** and is per-instance. `jay-content` is its mirror on the **template** side —
450
+ and because you [author templates too](#creating-a-design-system-element), it is a tool you reach for when
451
+ _creating_ an element, not something only plugin authors use. Put it on a template node to pre-declare a
452
+ **content slot**: "this part is meant to be filled per page." Edits to a marked facet are then expected by
453
+ design — `validate` never reports them as drift, and `sync` keeps the consumer's version — **without** any
454
+ page-side `override`.
455
+
456
+ ```html
457
+ <!-- in a template you author or own: -->
458
+ <p jay-content>{body}</p>
459
+ <img jay-content="src alt" src="placeholder.png" alt="" />
460
+ ```
461
+
462
+ ```html
463
+ <!-- in each flattened page, just edit — no override needed: -->
464
+ <p>Everything your team needs, in one place.</p>
465
+ <img src="/hero.png" alt="Our team at work" />
466
+ ```
467
+
468
+ **`jay-content` is optional, not required.** It is purely a convenience that removes the need to mark
469
+ `override` on the slot. Unlike slot/children mechanisms in other frameworks, you do **not** have to annotate
470
+ every variable spot — an unmarked content edit still works; it just shows up as drift until you either mark
471
+ it (`override` on the page, or `jay-content` on the template) or `sync` it away. Reach for `jay-content` when
472
+ a node is _meant_ to vary across pages and you'd rather not repeat an `override` on every instance.
473
+
474
+ It is a **template-side** marker — it lives on the source template and is stripped from the flattened page
475
+ (so it is stated once, and resilient to template edits). It uses the **same facet-list grammar** as
476
+ `override` (`children`, `style.<prop>`, attribute names, `*` — see
477
+ [_Marking what you own_](#marking-what-you-own-facets)), with one difference: a **bare** `jay-content` means
478
+ **children** (the common text slot), whereas a bare `override` means the whole node.
479
+
480
+ ## Upgrading: `jay-stack sync`
481
+
482
+ When the source template changes (a component upgrade, a design fix), re-flatten:
483
+
484
+ ```bash
485
+ jay-stack sync # every design-system region in the project
486
+ jay-stack sync src/pages/home.jay-html # one page
487
+ jay-stack sync --all # explicit "all pages"
488
+ ```
489
+
490
+ Sync is **re-flatten, not merge**: it overwrites everything from the current source **except** the facets
491
+ you marked `override`, which it preserves verbatim. There is no merge base, no hash, no conflict prompt —
492
+ your region is by definition equal to its source except at your marked facets, so sync is deterministic
493
+ overwrite-with-holes. It can never silently combine two edited versions into wrong markup. A synced region
494
+ validates clean.
495
+
496
+ CSS reconciles the same way as markup — drift is overwritten, marked facets are kept:
497
+
498
+ - A region block **equal** to the canonical form (or missing) is (re-)emitted as the canonical
499
+ `@scope (.<ref>)` block, coalesced with its same-template siblings.
500
+ - A block that **diverges but carries a `/* jay:override */` pragma** is preserved verbatim — your pragma is
501
+ never rewritten.
502
+ - A block that **diverges without any pragma** is unmarked CSS drift: `sync` overwrites it back to the
503
+ canonical form, exactly as it re-flattens unmarked markup. (This is what lets `sync` _migrate_ a page to a
504
+ new canonical form — e.g. an older `:scope { … }` / ref-on-root block is rewritten to the DL#206 form:
505
+ plain class rules wrapped in `@scope (.<ref>)`.)
506
+
507
+ So after one `sync` a page's CSS is in canonical form, and a `validate`-clean page is one `sync` leaves
508
+ unchanged. To keep a hand-edit through `sync`, mark it with a `jay:override` pragma.
509
+
510
+ ## Suppressing the preference warnings
511
+
512
+ Each "prefer design-system elements" warning is suppressible for the legitimate case:
513
+
514
+ | Rule | Suppress |
515
+ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
516
+ | `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) |
517
+ | `REGION-OVERRIDE-NON-CONTENT` | `jay-validations="REGION-OVERRIDE-NON-CONTENT"` on the import |
518
+ | `COMPONENT-NO-TEMPLATE` | `allow-no-template: [Contract]` (a data-only component has no `.jay-html` to host an attribute) |
519
+ | `NO-DESIGN-SYSTEM` | `allow-no-design-system: true` |
520
+
521
+ Per-region-type suppression lives on the import; put multiple rules in one attribute:
522
+
523
+ ```html
524
+ <script
525
+ type="application/jay-headless"
526
+ contract="./components/card/card.jay-contract"
527
+ jay-validations="REGION-NOT-LINKED REGION-OVERRIDE-NON-CONTENT"
528
+ ></script>
529
+ ```
530
+
531
+ Project-wide / list suppression lives in the page's validations script:
532
+
533
+ ```html
534
+ <script type="application/jay-validations">
535
+ jay-stack:
536
+ allow-inline-region: [Card, HeroBanner] # contract names — allow hand-authored regions
537
+ allow-no-template: [MetricsProvider] # data-only components with no UI
538
+ allow-no-design-system: true # project intentionally shares no design system
539
+ </script>
540
+ ```
541
+
542
+ ## Why copy instead of reference?
543
+
544
+ Flattening removes the composition "seam": the region compiles through the ordinary headless-instance path,
545
+ so there is **no runtime crossing machinery** and no per-component ref-scope rules. The cost is that page
546
+ files grow and a copy duplicates its source — which is exactly what `validate` (drift) and `sync`
547
+ (re-flatten) exist to manage. Design edits stay in the page; the source stays reusable.