@nexart/ui-renderer 0.8.7 → 0.9.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @nexart/ui-renderer
2
2
 
3
- Version: 0.8.7
3
+ Version: 0.9.0
4
4
 
5
5
  **Lightweight Preview Runtime for NexArt Protocol**
6
6
 
@@ -19,124 +19,191 @@ Version: 0.8.7
19
19
 
20
20
  ---
21
21
 
22
- ## v0.8.7 Live Runtime Binding Fix
23
-
24
- Fixed critical bug where time-varying properties were frozen in loop mode.
25
-
26
- - **Live binding**: `frameCount`, `t`, `time`, `tGlobal`, `totalFrames` now update every frame
27
- - **Proxy + with pattern**: Uses a Proxy scope with `with()` for live property access
28
- - **Animations work**: Sketches see correct values on every frame
29
-
30
- **Animation Semantics:**
31
- In loop mode, all time variables update every frame:
32
- - `frameCount`: Increments each frame
33
- - `t`: Normalized time `(frameCount % totalFrames) / totalFrames` (range [0,1))
34
- - `time`: Alias for `t`
35
- - `tGlobal`: Alias for `t`
36
- - `totalFrames`: Total frames in the loop (default 120)
37
-
38
- **Test Sketch:**
39
- ```javascript
40
- function draw() {
41
- background(0);
42
- fill(255);
43
- circle(
44
- width / 2 + sin(frameCount * 0.1) * 300,
45
- height / 2,
46
- 120
47
- );
48
- }
49
- ```
50
- Expected: Circle moves horizontally, motion is continuous.
22
+ ## For AI Coding Agents (Replit / Lovable / Claude Code)
51
23
 
52
- ---
24
+ ### 20-Second Quickstart
25
+
26
+ ```typescript
27
+ import { createPreviewRuntime } from '@nexart/ui-renderer';
28
+
29
+ const runtime = createPreviewRuntime({
30
+ canvas: document.getElementById('canvas'),
31
+ source: `
32
+ function draw() {
33
+ background(30);
34
+ circle(width/2 + sin(t * TWO_PI) * 200, height/2, 100);
35
+ }
36
+ `,
37
+ mode: 'loop',
38
+ width: 1950,
39
+ height: 2400,
40
+ seed: 12345,
41
+ vars: [50, 75, 25],
42
+ totalFrames: 120,
43
+ onBudgetExceeded: (info) => console.warn('Budget exceeded:', info),
44
+ });
45
+
46
+ runtime.startLoop();
47
+ ```
53
48
 
54
- ## v0.8.3 Animation Loop Fix
49
+ Preview mode may visually differ due to scaling/budgets. For canonical output and verification, run the same code with `@nexart/codemode-sdk`.
55
50
 
56
- Fixed critical bug where preview only rendered a single frame.
51
+ ### Detecting Preview Mode
57
52
 
58
- - **RAF unconditionally scheduled**: `requestAnimationFrame(loop)` is now called on every tick
59
- - **Budget gates draw only**: Frame budget controls whether `draw()` runs, not whether the loop continues
60
- - **Continuous animation**: Loop runs forever until explicitly stopped
53
+ ```typescript
54
+ const stats = runtime.getPreviewStats();
61
55
 
62
- **Animation Loop Invariant (locked for v0.x):**
56
+ if (stats.mode === 'preview') {
57
+ console.log('Running in preview mode');
58
+ console.log('Scale:', stats.scale);
59
+ console.log('Frames:', stats.frames);
60
+ }
63
61
  ```
64
- RAF schedules → budget checks → draw executes (or skips) → repeat
65
- Never: budget check → stop loop
62
+
63
+ ### Handling Budget Exceeded
64
+
65
+ ```typescript
66
+ const stats = runtime.getPreviewStats();
67
+
68
+ if (stats.budgetExceeded) {
69
+ console.log('Exceeded:', stats.budgetExceeded.reason);
70
+ // Options:
71
+ // 1. Reduce sketch complexity (fewer iterations, simpler shapes)
72
+ // 2. Increase maxFrames or maxTimeMs in config
73
+ // 3. Use @nexart/codemode-sdk for canonical execution
74
+ }
66
75
  ```
67
76
 
68
77
  ---
69
78
 
70
- ## v0.8.2 Runtime Dimensions Fix
79
+ ## Preview vs Canonical
71
80
 
72
- Fixed critical bug where preview scaling affected `width`/`height` inside sketches.
81
+ | Aspect | Preview (@nexart/ui-renderer) | Canonical (@nexart/codemode-sdk) |
82
+ |--------|-------------------------------|----------------------------------|
83
+ | **Purpose** | Fast browser previews | Authoritative rendering |
84
+ | **Determinism** | Approximate | Guaranteed |
85
+ | **Resolution** | Max 900px | Full resolution (1950×2400) |
86
+ | **FPS** | Native RAF (~60 FPS) | Full frame rate |
87
+ | **Budget** | 1800 frames / 5 min | Unlimited |
88
+ | **Use for** | Editor, dashboard, prototyping | Minting, export, archival |
73
89
 
74
- - **Runtime uses original dimensions**: `width` and `height` now match Code Mode exactly
75
- - **Canvas buffer still scaled for performance**: Rendering is fast, semantics are correct
76
- - **Loop animations work correctly**: Geometry math and timing no longer break
77
-
78
- **Key rule enforced:** Preview scaling is a rendering concern, not a semantic one.
90
+ **Rule of thumb:**
91
+ - Use **ui-renderer** for quick visual feedback during development
92
+ - Use **codemode-sdk** for final output, minting, or any permanent storage
79
93
 
80
94
  ---
81
95
 
82
- ## v0.8.1Canvas Scaling Fix
96
+ ## v0.9.0Budget Callbacks + Observability
83
97
 
84
- Fixed canvas zoom/cropping bug caused by resolution downscaling in v0.8.0.
98
+ ### New Features
85
99
 
86
- - **Scale Transform Reapplied**: Context scale is now properly restored after canvas resize
87
- - **Transform-Safe Clear**: `clearRect` now ignores active transforms for correct full-canvas clearing
88
- - **No API Changes**: All fixes are internal
100
+ 1. **No more silent stops** Budget exceeded now triggers callbacks and optional overlay
101
+ 2. **getPreviewStats()** Full observability into runtime state
102
+ 3. **createPreviewRuntime()** Recommended entrypoint for agents
103
+ 4. **toCanonicalRequest()** — Handoff helper for @nexart/codemode-sdk
89
104
 
90
- ---
105
+ ### Budget Configuration
106
+
107
+ ```typescript
108
+ const runtime = createPreviewRuntime({
109
+ canvas,
110
+ source: sketchCode,
111
+ mode: 'loop',
112
+ width: 1950,
113
+ height: 2400,
114
+
115
+ // Budget options (all optional)
116
+ maxFrames: 1800, // Max frames before budget exceeded
117
+ maxTimeMs: 300000, // Max time (5 min default)
118
+ budgetBehavior: 'stop', // 'stop' (with overlay) or 'degrade' (skip frames)
119
+ showOverlay: true, // Show overlay when stopped
120
+
121
+ onBudgetExceeded: (info) => {
122
+ console.log('Budget exceeded:', info.reason);
123
+ console.log('Frames:', info.framesRendered);
124
+ console.log('Time:', info.totalTimeMs);
125
+ },
126
+ });
127
+ ```
128
+
129
+ ### Preview Stats
91
130
 
92
- ## v0.8.0 — Lightweight Preview Runtime
131
+ ```typescript
132
+ const stats = runtime.getPreviewStats();
133
+ // {
134
+ // mode: 'preview',
135
+ // scale: 0.46, // Canvas scale factor
136
+ // semanticWidth: 1950, // Original width (for sketch math)
137
+ // semanticHeight: 2400, // Original height
138
+ // bufferWidth: 897, // Actual canvas width
139
+ // bufferHeight: 900, // Actual canvas height
140
+ // frames: 150, // Frames rendered
141
+ // stride: 1, // Current stride (1 = every frame)
142
+ // totalTimeMs: 18750, // Total time
143
+ // budgetExceeded?: { reason: 'frame_limit' | 'time_limit' }
144
+ // }
145
+ ```
93
146
 
94
- This release refactors the UI renderer into a performance-optimized preview runtime.
147
+ ### Canonical Handoff
95
148
 
96
- ### Philosophy Change
149
+ ```typescript
150
+ import { toCanonicalRequest } from '@nexart/ui-renderer';
97
151
 
98
- **The UI renderer is explicitly preview-only:**
99
- - No protocol errors thrown
100
- - No forbidden pattern checking
101
- - Soft warnings instead of hard failures
102
- - Permissive clamping instead of rejecting
152
+ const config = { canvas, source, mode: 'loop', width: 1950, height: 2400, seed: 12345 };
153
+ const req = toCanonicalRequest(config);
154
+
155
+ // Pass to backend for canonical rendering:
156
+ // await fetch('/api/render', { body: JSON.stringify(req) });
157
+ ```
158
+
159
+ ---
103
160
 
104
- ### Preview Budget (MANDATORY)
161
+ ## Troubleshooting
105
162
 
106
- The runtime enforces hard limits to prevent browser freezes:
163
+ ### "My sketch stops animating"
164
+
165
+ Check `getPreviewStats().budgetExceeded`:
107
166
 
108
167
  ```typescript
109
- PREVIEW_BUDGET = {
110
- MAX_FRAMES: 30, // Maximum frames before auto-stop
111
- MAX_TOTAL_TIME_MS: 500, // Maximum execution time
112
- FRAME_STRIDE: 3, // Render every 3rd frame
113
- };
168
+ const stats = runtime.getPreviewStats();
169
+ if (stats.budgetExceeded) {
170
+ console.log('Stopped because:', stats.budgetExceeded.reason);
171
+ }
114
172
  ```
115
173
 
116
- If limits exceeded: rendering stops silently (no throw).
174
+ **Solutions:**
175
+ 1. Reduce sketch complexity (fewer loop iterations, simpler shapes)
176
+ 2. Increase budget: `maxFrames: 3600` or `maxTimeMs: 600000`
177
+ 3. Use `budgetBehavior: 'degrade'` to continue at reduced frame rate
178
+ 4. For long-running animations, use `@nexart/codemode-sdk`
117
179
 
118
- ### Canvas Scaling
180
+ ### "Canvas is too small"
119
181
 
120
- Preview renderer scales large canvases automatically:
182
+ Preview scales canvases to max 900px for performance:
121
183
 
122
184
  ```typescript
123
- CANVAS_LIMITS = {
124
- MAX_DIMENSION: 900, // Max width/height in pixels
125
- MIN_DIMENSION: 100,
126
- };
185
+ const stats = runtime.getPreviewStats();
186
+ console.log('Buffer size:', stats.bufferWidth, 'x', stats.bufferHeight);
187
+ console.log('Semantic size:', stats.semanticWidth, 'x', stats.semanticHeight);
127
188
  ```
128
189
 
129
- Aspect ratio is preserved. CSS scaling for display.
190
+ Your sketch code sees `width` and `height` as the original values (1950×2400).
191
+ The canvas buffer is scaled down, but your geometry math stays correct.
130
192
 
131
- ### Runtime Mode Flag
193
+ ### "Animation looks choppy"
132
194
 
133
- The preview runtime exposes a `mode` property:
195
+ Preview runs at native RAF cadence (~60 FPS). If animations appear choppy:
134
196
 
197
+ 1. Check if budget exceeded (degrade mode skips frames):
135
198
  ```typescript
136
- runtime.mode // 'preview'
199
+ const stats = runtime.getPreviewStats();
200
+ if (stats.budgetExceeded && stats.stride > 1) {
201
+ console.log('Running in degraded mode, skipping frames');
202
+ }
137
203
  ```
138
204
 
139
- Use this to detect preview vs canonical rendering.
205
+ 2. Reduce sketch complexity (heavy draw() calls)
206
+ 3. Check browser performance (GPU acceleration, tab throttling)
140
207
 
141
208
  ---
142
209
 
@@ -149,243 +216,103 @@ npm install @nexart/ui-renderer
149
216
  Or use directly in HTML:
150
217
  ```html
151
218
  <script type="module">
152
- import { createSystem, previewSystem } from 'https://unpkg.com/@nexart/ui-renderer/dist/index.js';
219
+ import { createPreviewRuntime } from 'https://unpkg.com/@nexart/ui-renderer/dist/index.js';
153
220
  </script>
154
221
  ```
155
222
 
156
223
  ---
157
224
 
158
- ## Quick Start
159
-
160
- ### Code Mode Preview
161
-
162
- ```typescript
163
- import { createSystem, previewSystem } from '@nexart/ui-renderer';
164
-
165
- const system = createSystem({
166
- type: 'code',
167
- mode: 'loop',
168
- width: 1950,
169
- height: 2400,
170
- totalFrames: 120,
171
- seed: 12345,
172
- vars: [50, 75, 25], // VAR[0..9] — values 0-100, padded with zeros
173
- source: `
174
- function setup() {
175
- noFill();
176
- stroke(0);
177
- }
178
- function draw() {
179
- background(246, 245, 242);
180
- var count = int(map(VAR[0], 0, 100, 5, 30));
181
- for (var i = 0; i < count; i++) {
182
- var x = width / 2 + cos(t * TWO_PI + i * 0.5) * map(VAR[1], 0, 100, 50, 400);
183
- var y = height / 2 + sin(t * TWO_PI + i * 0.3) * 200;
184
- ellipse(x, y, 50);
185
- }
186
- }
187
- `
188
- });
189
-
190
- const canvas = document.getElementById('canvas');
191
- const renderer = previewSystem(system, canvas);
192
- renderer.start();
193
- ```
194
-
195
- ### Static Mode (single frame)
196
-
197
- ```typescript
198
- const staticSystem = createSystem({
199
- type: 'code',
200
- mode: 'static',
201
- width: 1950,
202
- height: 2400,
203
- seed: 12345,
204
- vars: [50, 50, 50],
205
- source: `
206
- function setup() {
207
- background(246, 245, 242);
208
- fill(0);
209
- var count = int(map(VAR[0], 0, 100, 10, 200));
210
- for (var i = 0; i < count; i++) {
211
- ellipse(random(width), random(height), random(10, 50));
212
- }
213
- }
214
- `
215
- });
216
-
217
- previewSystem(staticSystem, canvas).render();
218
- ```
219
-
220
- ---
221
-
222
225
  ## API Reference
223
226
 
224
- ### `createSystem(input)`
227
+ ### `createPreviewRuntime(config)` ⭐ Recommended
225
228
 
226
- Create a NexArt system for preview.
229
+ Create a preview renderer. This is the recommended entrypoint.
227
230
 
228
231
  ```typescript
229
- const system = createSystem({
230
- type: 'code',
232
+ const runtime = createPreviewRuntime({
233
+ canvas: HTMLCanvasElement,
234
+ source: string, // p5-like sketch code
231
235
  mode: 'static' | 'loop',
232
236
  width: number,
233
237
  height: number,
234
- source: string, // Raw p5-like sketch code
235
- seed?: number, // PRNG seed
236
- totalFrames?: number, // Required for loop mode
237
- vars?: number[] // VAR[0..9] (0-10 values, 0-100 range, clamped)
238
+ seed?: number, // PRNG seed
239
+ vars?: number[], // VAR[0..9] (0-100 range)
240
+ totalFrames?: number, // For loop mode (default: 120)
241
+
242
+ // v0.9.0 budget options
243
+ onBudgetExceeded?: (info: BudgetExceededInfo) => void,
244
+ budgetBehavior?: 'stop' | 'degrade',
245
+ showOverlay?: boolean,
246
+ maxFrames?: number,
247
+ maxTimeMs?: number,
238
248
  });
249
+
250
+ runtime.startLoop(); // Start animation
251
+ runtime.stopLoop(); // Stop animation
252
+ runtime.renderStatic(); // Render single frame
253
+ runtime.getPreviewStats(); // Get current stats
254
+ runtime.previewScale; // Canvas scale factor
255
+ runtime.destroy(); // Clean up
239
256
  ```
240
257
 
241
- ### `previewSystem(system, canvas, options?)`
258
+ ### `createSystem(input)` + `previewSystem(system, canvas)`
242
259
 
243
- Render a preview to canvas.
260
+ Legacy API for declarative systems. Still supported:
244
261
 
245
262
  ```typescript
246
- const renderer = previewSystem(system, canvas, {
247
- showBadge: boolean // default: true — shows "⚠️ Preview" badge
263
+ import { createSystem, previewSystem } from '@nexart/ui-renderer';
264
+
265
+ const system = createSystem({
266
+ type: 'code',
267
+ mode: 'loop',
268
+ width: 1950,
269
+ height: 2400,
270
+ source: sketchCode,
271
+ seed: 12345,
272
+ vars: [50, 75, 25],
248
273
  });
249
274
 
250
- renderer.render(); // Single frame
251
- renderer.start(); // Start loop
252
- renderer.stop(); // Stop loop
253
- renderer.destroy(); // Cleanup
275
+ const renderer = previewSystem(system, canvas);
276
+ renderer.start();
254
277
  ```
255
278
 
256
- ### `validateSystem(input)`
279
+ ### `toCanonicalRequest(config)`
257
280
 
258
- Soft validation warns but doesn't throw for minor issues.
281
+ Generate a request object for handoff to `@nexart/codemode-sdk`:
259
282
 
260
283
  ```typescript
261
- const result = validateSystem(input);
262
- if (!result.valid) {
263
- console.log(result.errors); // Structural issues only
264
- }
284
+ import { toCanonicalRequest } from '@nexart/ui-renderer';
285
+
286
+ const req = toCanonicalRequest(config);
287
+ // { seed, vars, code, settings, renderer: 'preview', uiRendererVersion }
265
288
  ```
266
289
 
267
290
  ### `getCapabilities()`
268
291
 
269
- Discover SDK capabilities for AI and tooling.
292
+ Discover SDK capabilities:
270
293
 
271
294
  ```typescript
272
295
  import { getCapabilities } from '@nexart/ui-renderer';
273
296
 
274
297
  const caps = getCapabilities();
275
- // {
276
- // version: '0.8.7',
277
- // isCanonical: false,
278
- // isArchival: false,
279
- // previewBudget: { MAX_FRAMES: 30, MAX_TOTAL_TIME_MS: 500, FRAME_STRIDE: 3 },
280
- // canvasLimits: { MAX_DIMENSION: 900 },
281
- // ...
282
- // }
298
+ // { version, isCanonical, previewBudget, canvasLimits, ... }
283
299
  ```
284
300
 
285
301
  ---
286
302
 
287
303
  ## Available Helpers
288
304
 
289
- The preview runtime includes all standard p5-like functions plus:
305
+ The preview runtime includes all standard p5-like functions:
290
306
 
291
307
  | Helper | Description |
292
308
  |--------|-------------|
293
- | `int(n)` | Convert to integer (Math.floor) |
294
- | `fract(n)` | Fractional part of number |
295
- | `sign(n)` | Sign of number |
309
+ | `int(n)` | Convert to integer |
310
+ | `fract(n)` | Fractional part |
296
311
  | `vec(x, y)` | Create 2D vector |
297
312
  | `polygon(x, y, r, n)` | Draw n-sided polygon |
298
313
  | `star(x, y, r1, r2, n)` | Draw n-pointed star |
299
314
  | `fbm(x, y, z, octaves)` | Fractal Brownian motion |
300
- | `easeIn(t)`, `easeOut(t)`, etc. | Easing functions |
301
-
302
- ### Control Functions
303
-
304
- | Function | Preview Behavior |
305
- |----------|------------------|
306
- | `noLoop()` | Stops preview loop (soft control) |
307
- | `loop()` | No-op in preview |
308
-
309
- ---
310
-
311
- ## Supported Element Types
312
-
313
- ### `background`
314
-
315
- Opinionated presets with guardrails.
316
-
317
- ```typescript
318
- {
319
- type: 'background',
320
- style: 'gradient', // gradient, solid, noise, grain
321
- palette: 'warm' // warm, cool, neutral, vibrant
322
- }
323
- ```
324
-
325
- ### `primitive`
326
-
327
- Declarative generative components. **30 primitives available:**
328
-
329
- **Categories:**
330
- - `basic` — dots, lines, waves, stripes, circles, grid
331
- - `geometric` — polygons, diamonds, hexgrid, stars, concentricSquares
332
- - `radial` — spirals, rays, orbits, rings, arcs, radialLines, petals
333
- - `flow` — flow, particles, bubbles
334
- - `patterns` — crosshatch, chevrons, zigzag, weave, moire
335
- - `organic` — curves, noise, mesh, branches
336
-
337
- ```typescript
338
- {
339
- type: 'primitive',
340
- name: 'spirals',
341
- count: 12,
342
- color: '#ffffff',
343
- motion: 'medium',
344
- strokeWeight: 'thin',
345
- opacity: 0.8
346
- }
347
- ```
348
-
349
- ### `sketch`
350
-
351
- Raw Code Mode execution.
352
-
353
- ```typescript
354
- {
355
- type: 'sketch',
356
- code: `
357
- function setup() { background(0); }
358
- function draw() { ellipse(width/2, height/2, 100); }
359
- `,
360
- normalize: true
361
- }
362
- ```
363
-
364
- ---
365
-
366
- ## VAR Handling (Soft)
367
-
368
- VAR[0..9] is an array of 10 protocol variables.
369
-
370
- **Preview behavior (permissive):**
371
- - Input: 0-10 elements accepted (extras ignored with warning)
372
- - Values: Clamped to 0-100 (not rejected)
373
- - Invalid types: Replaced with 0 (not rejected)
374
- - Runtime: Always 10 elements (padded with zeros)
375
- - Access: Read-only
376
-
377
- ```typescript
378
- // Works in preview (clamped to 100)
379
- vars: [150, 50, 50] // VAR[0] becomes 100
380
-
381
- // Works in preview (non-numbers become 0)
382
- vars: ['bad', 50, 50] // VAR[0] becomes 0
383
-
384
- // Works in preview (extras ignored)
385
- vars: [1,2,3,4,5,6,7,8,9,10,11,12] // Uses first 10
386
- ```
387
-
388
- For strict validation, use `@nexart/codemode-sdk`.
315
+ | `easeIn(t)`, `easeOut(t)` | Easing functions |
389
316
 
390
317
  ---
391
318
 
@@ -396,7 +323,7 @@ For strict validation, use `@nexart/codemode-sdk`.
396
323
  | Protocol enforcement | No pattern checking or validation errors |
397
324
  | Canonical output | Use @nexart/codemode-sdk for minting |
398
325
  | Determinism guarantee | Preview may vary slightly |
399
- | Full frame count | Limited to 30 frames max |
326
+ | Full frame count | Limited by budget (default 1800 frames) |
400
327
  | Full resolution | Limited to 900px max dimension |
401
328
 
402
329
  ---
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @nexart/ui-renderer v0.8.0 - Capabilities Discovery
2
+ * @nexart/ui-renderer v0.9.0 - Capabilities Discovery
3
3
  *
4
4
  * Exposes SDK capabilities for AI tools and builders.
5
5
  * Lightweight preview runtime — no protocol enforcement.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @nexart/ui-renderer v0.8.0 - Capabilities Discovery
2
+ * @nexart/ui-renderer v0.9.0 - Capabilities Discovery
3
3
  *
4
4
  * Exposes SDK capabilities for AI tools and builders.
5
5
  * Lightweight preview runtime — no protocol enforcement.
@@ -58,7 +58,7 @@ function createPrimitive(name, category, description) {
58
58
  }
59
59
  export function getCapabilities() {
60
60
  return {
61
- version: '0.8.0',
61
+ version: '0.9.0',
62
62
  isCanonical: false,
63
63
  isArchival: false,
64
64
  renderer: '@nexart/ui-renderer',