@jay-framework/jay-stack-cli 0.23.0 → 0.24.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.
@@ -124,6 +124,8 @@ This generates both a ViewState field and a ref.
124
124
 
125
125
  Refs are the **only supported path** from TypeScript to elements Jay renders. Direct `document` access bypasses the framework and can break rendering, updates, and performance.
126
126
 
127
+ `jay-stack validate` warns on `document.querySelector`, `document.getElementById`, `document.createElement`, and `document.addEventListener` in page and component `.ts` files. Suppress with `// jay-dom: allow` on the same line when an exception is genuinely needed.
128
+
127
129
  ### Do
128
130
 
129
131
  - Declare elements in **jay-html** with `ref="..."`.
@@ -134,20 +136,135 @@ Refs are the **only supported path** from TypeScript to elements Jay renders. Di
134
136
 
135
137
  ### Avoid
136
138
 
137
- - `document.querySelector` / `getElementById` to find template elements
138
- - `document.createElement` + `appendChild` for UI that belongs in jay-html
139
- - `document.addEventListener('mousemove'|'mouseup')` for drags (use pointer capture instead)
139
+ - `document.querySelector` / `getElementById` to find template elements — use refs
140
+ - `document.createElement` + `appendChild` for UI that belongs in jay-html — use `forEach` with ViewState
141
+ - `document.addEventListener` for global events — use the root ref pattern (below)
140
142
 
141
- ### Rare `document` exceptions
143
+ ## Root ref pattern — replacing `document.addEventListener`
144
+
145
+ Wrap the page content in a shell element with a ref. Use capture-phase listeners on the shell to intercept events before they reach children — functionally equivalent to `document.addEventListener`.
146
+
147
+ ### Setup
148
+
149
+ ```html
150
+ <!-- jay-html -->
151
+ <div ref="shell" class="page-shell">... entire page content ...</div>
152
+ ```
153
+
154
+ ```yaml
155
+ # Contract
156
+ - tag: shell
157
+ type: interactive
158
+ elementType: HTMLDivElement
159
+ ```
160
+
161
+ ### Global keyboard navigation
162
+
163
+ Instead of `document.addEventListener('keydown', ...)`:
164
+
165
+ ```typescript
166
+ .withInteractive(function Page(_props, refs) {
167
+ let keyboardNav = false;
168
+
169
+ refs.shell.onkeydown(({ event }) => {
170
+ if (event.key === 'Tab') keyboardNav = true;
171
+ });
172
+
173
+ refs.shell.onmousedown(() => {
174
+ keyboardNav = false;
175
+ });
176
+ })
177
+ ```
178
+
179
+ Events bubble up from children to the shell — a handler on the shell sees all keyboard and mouse events from the entire page.
180
+
181
+ Use `refs.shell.addEventListener(type, handler, { capture: true })` only when you need to intercept events _before_ children handle them (e.g., preventing default on specific keys).
182
+
183
+ ### Focus management (scroll into view)
184
+
185
+ Instead of `document.addEventListener('focusin', ...)`:
186
+
187
+ ```typescript
188
+ refs.shell.onfocusin(({ event }) => {
189
+ if (!keyboardNav) return;
190
+ const el = event.target as HTMLElement;
191
+ refs.shell.exec$(() => {
192
+ el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
193
+ });
194
+ });
195
+ ```
196
+
197
+ ### Detecting pointer leaving the page
198
+
199
+ Instead of `document.addEventListener('mouseleave', ...)`:
142
200
 
143
- Use only when no ref can exist, with an inline comment:
201
+ ```typescript
202
+ refs.shell.onpointerleave(({ event }) => {
203
+ // Pointer left the shell — equivalent to leaving the viewport
204
+ // if the shell covers the full viewport
205
+ });
206
+ ```
207
+
208
+ Ensure the shell has no margin/padding gap so it covers the full viewport. `pointer-events: auto` (the default) is sufficient.
209
+
210
+ ### Finding elements by class → use refs
211
+
212
+ Instead of `document.querySelector('.site-header')`:
213
+
214
+ ```html
215
+ <!-- jay-html -->
216
+ <header ref="siteHeader" class="site-header">...</header>
217
+ ```
218
+
219
+ ```typescript
220
+ refs.siteHeader.onclick(() => {
221
+ /* ... */
222
+ });
223
+
224
+ refs.siteHeader.exec$((el) => {
225
+ el.classList.add('is-hidden');
226
+ });
227
+ ```
228
+
229
+ ### Dynamic lists → use forEach
144
230
 
145
- | Case | Example |
146
- | ---------------------- | ----------------------------------------------------- |
147
- | Offscreen processing | `document.createElement('canvas')` for image export |
148
- | Coordinate hit-testing | `document.elementFromPoint` during cross-overlay drag |
149
- | Tests | `document.dispatchEvent` in Vitest |
231
+ Instead of creating elements with `document.createElement` in a loop:
232
+
233
+ ```html
234
+ <!-- jay-html -->
235
+ <div forEach="cards" trackBy="id" class="card-grid">
236
+ <div class="card">
237
+ <img src="{imageUrl}" alt="{title}" />
238
+ <span>{title}</span>
239
+ </div>
240
+ </div>
241
+ ```
242
+
243
+ Update the ViewState to add/remove cards — the framework handles DOM creation.
244
+
245
+ ### Using `exec$` for native DOM APIs
246
+
247
+ For DOM operations that refs don't wrap (scroll, focus, measurements), use `exec$` inside an event handler:
248
+
249
+ ```typescript
250
+ refs.myInput.onclick(() => {
251
+ refs.myInput.exec$((el) => {
252
+ el.focus();
253
+ el.select();
254
+ });
255
+ });
256
+
257
+ refs.scrollContainer.exec$((el) => {
258
+ el.scrollTo({ top: 0, behavior: 'smooth' });
259
+ });
260
+ ```
261
+
262
+ ### Rare `document` exceptions
150
263
 
151
- Global shortcuts or paste: prefer a root shell ref (`ref="appRoot"`) with capture listeners.
264
+ Use only when no ref can exist, with `// jay-dom: allow` to suppress the validation warning:
152
265
 
153
- See also: `.cursor/skills/jay-dom-refs/SKILL.md` in the jay monorepo.
266
+ | Case | Example |
267
+ | ---------------------- | --------------------------------------------------------------------- |
268
+ | Offscreen processing | `document.createElement('canvas') // jay-dom: allow` for image export |
269
+ | Coordinate hit-testing | `document.elementFromPoint(...) // jay-dom: allow` during drag |
270
+ | Tests | `document.dispatchEvent // jay-dom: allow` in Vitest |
@@ -44,10 +44,10 @@ Use `reference` for data sources (contracts, categories). Use `stage-place` for
44
44
  Controls card size in the browse grid:
45
45
 
46
46
  ```yaml
47
- browse:
48
- size: small # 4 per row — color swatches, small tokens
49
- size: medium # 2 per row — default
50
- size: large # 1 per row — full-width previews
47
+ browse:
48
+ size: small # 4 per row — color swatches, small tokens
49
+ size: medium # 2 per row — default
50
+ size: large # 1 per row — full-width previews
51
51
  ```
52
52
 
53
53
  ## Presentation (preview)
@@ -55,19 +55,19 @@ Controls card size in the browse grid:
55
55
  Optional visual preview in the browse grid:
56
56
 
57
57
  ```yaml
58
- # Static image
59
- presentation:
60
- type: image
61
- src: thumbnails/my-plugin/feature.png
62
-
63
- # HTML fragment — must use @scope for CSS isolation
64
- presentation:
65
- type: html-fragment
66
- html: |
67
- <div>
68
- <style>@scope { .demo { color: blue; } }</style>
69
- <div class="demo">Preview content</div>
70
- </div>
58
+ # Static image
59
+ presentation:
60
+ type: image
61
+ src: thumbnails/my-plugin/feature.png
62
+
63
+ # HTML fragment — must use @scope for CSS isolation
64
+ presentation:
65
+ type: html-fragment
66
+ html: |
67
+ <div>
68
+ <style>@scope { .demo { color: blue; } }</style>
69
+ <div class="demo">Preview content</div>
70
+ </div>
71
71
  ```
72
72
 
73
73
  Html-fragment rules:
package/dist/index.d.ts CHANGED
@@ -17,9 +17,12 @@ interface JayConfig {
17
17
  publicFolder?: string;
18
18
  configBase?: string;
19
19
  };
20
+ site?: {
21
+ baseUrl?: string;
22
+ };
20
23
  }
21
24
  declare function loadConfig(): JayConfig;
22
- declare function getConfigWithDefaults(config: JayConfig): Required<JayConfig>;
25
+ declare function getConfigWithDefaults(config: JayConfig): Required<Pick<JayConfig, 'devServer'>> & Pick<JayConfig, 'site'>;
23
26
  declare function updateConfig(updates: Partial<JayConfig>): void;
24
27
 
25
28
  export { type JayConfig, type StartDevServerOptions, getConfigWithDefaults, loadConfig, startDevServer, updateConfig };