@viewscript/renderer 0.1.0-202605140639

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 (89) hide show
  1. package/dist/ast/types.d.ts +403 -0
  2. package/dist/ast/types.js +33 -0
  3. package/dist/compiler/chunk-splitter.d.ts +98 -0
  4. package/dist/compiler/chunk-splitter.js +361 -0
  5. package/dist/index.d.ts +55 -0
  6. package/dist/index.js +17 -0
  7. package/dist/rasterizer/__tests__/error-distribution.test.d.ts +7 -0
  8. package/dist/rasterizer/__tests__/error-distribution.test.js +322 -0
  9. package/dist/rasterizer/canvas-mapper.d.ts +280 -0
  10. package/dist/rasterizer/canvas-mapper.js +414 -0
  11. package/dist/rasterizer/error-distribution.d.ts +143 -0
  12. package/dist/rasterizer/error-distribution.js +231 -0
  13. package/dist/rasterizer/gradient-mapper.d.ts +223 -0
  14. package/dist/rasterizer/gradient-mapper.js +352 -0
  15. package/dist/rasterizer/topology-rounding.d.ts +151 -0
  16. package/dist/rasterizer/topology-rounding.js +347 -0
  17. package/dist/runtime/__tests__/event-backpressure.test.d.ts +10 -0
  18. package/dist/runtime/__tests__/event-backpressure.test.js +190 -0
  19. package/dist/runtime/event-backpressure.d.ts +393 -0
  20. package/dist/runtime/event-backpressure.js +458 -0
  21. package/dist/runtime/render-loop.d.ts +277 -0
  22. package/dist/runtime/render-loop.js +435 -0
  23. package/dist/runtime/wasm-resource-manager.d.ts +122 -0
  24. package/dist/runtime/wasm-resource-manager.js +253 -0
  25. package/dist/runtime/wgpu-renderer-adapter.d.ts +168 -0
  26. package/dist/runtime/wgpu-renderer-adapter.js +230 -0
  27. package/dist/semantic/__tests__/semantic-translator.test.d.ts +4 -0
  28. package/dist/semantic/__tests__/semantic-translator.test.js +203 -0
  29. package/dist/semantic/semantic-translator.d.ts +229 -0
  30. package/dist/semantic/semantic-translator.js +398 -0
  31. package/package.json +28 -0
  32. package/playwright-report/data/0bafe4e0863f0e244bba68a838f73241f8f2efaa.md +226 -0
  33. package/playwright-report/data/9281aca8abfb06c6cecb35d5ddd13d61f8c752d8.md +226 -0
  34. package/playwright-report/index.html +90 -0
  35. package/playwright.config.ts +160 -0
  36. package/screenshot-chrome.png +0 -0
  37. package/screenshots/visual-demo-verification.png +0 -0
  38. package/screenshots/visual-demo.png +0 -0
  39. package/src/ast/types.ts +473 -0
  40. package/src/compiler/chunk-splitter.ts +534 -0
  41. package/src/index.ts +62 -0
  42. package/src/rasterizer/__tests__/error-distribution.test.ts +382 -0
  43. package/src/rasterizer/canvas-mapper.ts +677 -0
  44. package/src/rasterizer/error-distribution.ts +344 -0
  45. package/src/rasterizer/gradient-mapper.ts +563 -0
  46. package/src/rasterizer/topology-rounding.ts +499 -0
  47. package/src/runtime/__tests__/event-backpressure.test.ts +254 -0
  48. package/src/runtime/event-backpressure.ts +622 -0
  49. package/src/runtime/render-loop.ts +660 -0
  50. package/src/runtime/wasm-resource-manager.ts +349 -0
  51. package/src/runtime/wgpu-renderer-adapter.ts +318 -0
  52. package/src/semantic/__tests__/semantic-translator.test.ts +263 -0
  53. package/src/semantic/semantic-translator.ts +637 -0
  54. package/test-results/.last-run.json +4 -0
  55. package/tests/e2e/async-race.spec.ts +612 -0
  56. package/tests/e2e/bilayer-sync.spec.ts +405 -0
  57. package/tests/e2e/failures/.gitkeep +0 -0
  58. package/tests/e2e/fullstack.spec.ts +681 -0
  59. package/tests/e2e/g1-continuity.spec.ts +703 -0
  60. package/tests/e2e/golden/.gitkeep +0 -0
  61. package/tests/e2e/golden/conic-color-wheel.raw +0 -0
  62. package/tests/e2e/golden/conic-color-wheel.sha256 +1 -0
  63. package/tests/e2e/golden/conic-rotated.raw +0 -0
  64. package/tests/e2e/golden/conic-rotated.sha256 +1 -0
  65. package/tests/e2e/golden/linear-45deg.raw +0 -0
  66. package/tests/e2e/golden/linear-45deg.sha256 +1 -0
  67. package/tests/e2e/golden/linear-horizontal.raw +0 -0
  68. package/tests/e2e/golden/linear-horizontal.sha256 +1 -0
  69. package/tests/e2e/golden/linear-multi-stop.raw +0 -0
  70. package/tests/e2e/golden/linear-multi-stop.sha256 +1 -0
  71. package/tests/e2e/golden/radial-circle-center.raw +0 -0
  72. package/tests/e2e/golden/radial-circle-center.sha256 +1 -0
  73. package/tests/e2e/golden/radial-offset.raw +0 -0
  74. package/tests/e2e/golden/radial-offset.sha256 +1 -0
  75. package/tests/e2e/golden/tile-mirror.raw +0 -0
  76. package/tests/e2e/golden/tile-mirror.sha256 +1 -0
  77. package/tests/e2e/golden/tile-repeat.raw +0 -0
  78. package/tests/e2e/golden/tile-repeat.sha256 +1 -0
  79. package/tests/e2e/gradient-animation.spec.ts +606 -0
  80. package/tests/e2e/memory-stability.spec.ts +396 -0
  81. package/tests/e2e/path-topology.spec.ts +674 -0
  82. package/tests/e2e/performance-profile.spec.ts +501 -0
  83. package/tests/e2e/screenshot.spec.ts +60 -0
  84. package/tests/e2e/test-harness.html +1005 -0
  85. package/tests/e2e/text-layout.spec.ts +451 -0
  86. package/tests/e2e/visual-demo.html +340 -0
  87. package/tests/e2e/visual-regression.spec.ts +335 -0
  88. package/tsconfig.json +12 -0
  89. package/vitest.config.ts +8 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Atomic Render Loop for ViewScript
3
+ *
4
+ * This module implements a frame-synchronous render pipeline that guarantees
5
+ * Canvas and DOM layers are updated atomically within a single animation frame.
6
+ *
7
+ * ## Architectural Invariant: No Frame Tearing
8
+ *
9
+ * If a button's visual representation moves at frame N, its DOM hit region
10
+ * MUST also move at frame N. Any desynchronization causes Q-dimension inputs
11
+ * to "hit the void" - a catastrophic UX failure.
12
+ *
13
+ * ## Strategy: Double-Buffered Dirty Tracking
14
+ *
15
+ * ```
16
+ * Frame N:
17
+ * ┌─────────────────────────────────────────────────────────────────┐
18
+ * │ 1. Flush pending T-vector mutations (from Q-dimension events) │
19
+ * │ 2. Evaluate constraint graph (P-dimension solver) │
20
+ * │ 3. Topology-preserving rounding (with error distribution) │
21
+ * │ 4. Diff against previous frame's RasterBounds │
22
+ * │ 5. Batch Canvas draw commands (wgpu) │
23
+ * │ 6. Batch DOM style mutations (transform only, no reflow) │
24
+ * │ 7. Commit: GpuRenderer.flush() + requestAnimationFrame boundary│
25
+ * └─────────────────────────────────────────────────────────────────┘
26
+ * ```
27
+ *
28
+ * ## Reflow Prevention Strategy
29
+ *
30
+ * DOM mutations are restricted to compositor-only properties:
31
+ * - transform: translate3d(x, y, 0) - GPU-accelerated, no reflow
32
+ * - opacity - compositor-only
33
+ * - will-change: transform - hint to browser
34
+ *
35
+ * We NEVER touch: width, height, top, left, margin, padding (reflow triggers)
36
+ */
37
+ import type { EntityId, RenderableEntity, RasterBounds, PVectorBounds } from '../ast/types';
38
+ /**
39
+ * Semantic T-vector state keys.
40
+ * Mirrors the type from event-backpressure.ts.
41
+ */
42
+ type TStateKey = 'hover' | 'pressed' | 'focused' | 'scroll_x' | 'scroll_y' | 'drag_progress' | 'animation_t' | 'gesture_phase';
43
+ /**
44
+ * Pending T-vector state mutation from Q-dimension event.
45
+ *
46
+ * CRITICAL: These are SEMANTIC state mutations, not spatial coordinate
47
+ * assignments. P-dimension coordinates (X, Y, Z) are derived by the
48
+ * constraint solver as functions of T-vector state.
49
+ */
50
+ interface TStateMutation {
51
+ entityId: EntityId;
52
+ /** Semantic state key (hover, scroll_y, etc.) - NOT spatial coordinates */
53
+ state: TStateKey;
54
+ /** State value (boolean as 0/1, normalized 0-1, or discrete phase) */
55
+ value: number;
56
+ timestamp: number;
57
+ }
58
+ /**
59
+ * Canvas draw command (batched for GPU renderer).
60
+ */
61
+ interface DrawCommand {
62
+ entityId: EntityId;
63
+ type: 'path' | 'text' | 'image' | 'group';
64
+ bounds: RasterBounds;
65
+ payload: unknown;
66
+ }
67
+ /**
68
+ * Render loop configuration.
69
+ */
70
+ export interface RenderLoopConfig {
71
+ /** Target frames per second (default: 60) */
72
+ targetFPS: number;
73
+ /** Enable debug timing logs */
74
+ debugTiming: boolean;
75
+ /** Maximum mutations per frame (backpressure) */
76
+ maxMutationsPerFrame: number;
77
+ }
78
+ export declare class AtomicRenderLoop {
79
+ private config;
80
+ private frameState;
81
+ private pendingMutations;
82
+ private isRunning;
83
+ private rafHandle;
84
+ private constraintSolver;
85
+ private topologyRounder;
86
+ private canvasRenderer;
87
+ private domLayer;
88
+ /**
89
+ * Event buffer for async event atomicity.
90
+ *
91
+ * Injected via setEventBuffer() to support mergeAsyncEvents() at tick start.
92
+ */
93
+ private eventBuffer;
94
+ constructor(config: Partial<RenderLoopConfig>, constraintSolver: ConstraintSolver, topologyRounder: TopologyRounder, canvasRenderer: CanvasRenderer, domLayer: DOMLayer);
95
+ /**
96
+ * Start the render loop.
97
+ */
98
+ start(): void;
99
+ /**
100
+ * Stop the render loop.
101
+ */
102
+ stop(): void;
103
+ /**
104
+ * Queue a T-vector mutation from Q-dimension event.
105
+ * Called from event handlers (backpressure-controlled).
106
+ */
107
+ queueMutation(mutation: TStateMutation): void;
108
+ /**
109
+ * Set the event buffer for async event atomicity.
110
+ *
111
+ * The event buffer is used to merge async events (from fetch, setTimeout, etc.)
112
+ * at the start of each tick, ensuring deterministic event ordering.
113
+ */
114
+ setEventBuffer(buffer: EventBufferInterface): void;
115
+ /**
116
+ * Execute a single frame tick.
117
+ *
118
+ * CRITICAL: This function executes entirely within a single rAF callback,
119
+ * ensuring Canvas and DOM updates are committed atomically before the
120
+ * browser's compositor thread runs.
121
+ */
122
+ private tick;
123
+ private scheduleFrame;
124
+ /**
125
+ * Phase 1: Flush pending mutations with backpressure limit.
126
+ */
127
+ private flushMutations;
128
+ /**
129
+ * Phase 4: Compute which entities changed this frame.
130
+ */
131
+ private computeDirtySet;
132
+ /**
133
+ * Phase 5: Build Canvas draw commands for dirty entities only.
134
+ */
135
+ private buildDrawCommands;
136
+ /**
137
+ * Phase 6: Build DOM mutations (transform-only, no reflow).
138
+ *
139
+ * CRITICAL: We use transform: translate3d() which is compositor-only.
140
+ * This means the GPU handles the positioning without triggering
141
+ * the browser's layout engine.
142
+ */
143
+ private buildDOMMutations;
144
+ /**
145
+ * Phase 7: Atomic commit of Canvas and DOM updates.
146
+ *
147
+ * This function guarantees that by the time rAF callback returns:
148
+ * 1. All wgpu draw commands have been flushed to GPU
149
+ * 2. All DOM transforms have been written
150
+ * 3. Browser's compositor will see consistent state
151
+ *
152
+ * ## Strict DOM Proxy Enforcement
153
+ *
154
+ * DOM elements are initialized with `initializeDOMProxyElement()` which
155
+ * installs a Proxy guard. Only `transform` can be modified here.
156
+ */
157
+ private commitFrame;
158
+ /**
159
+ * Swap frame buffers for next frame's diff.
160
+ */
161
+ private swapBuffers;
162
+ }
163
+ /**
164
+ * Constraint solver interface.
165
+ *
166
+ * ## P/Q Boundary Enforcement
167
+ *
168
+ * The solver receives T-vector STATE mutations (hover, scroll_y, etc.)
169
+ * and returns P-dimension SPATIAL coordinates (X, Y, Z).
170
+ *
171
+ * The solver is the ONLY component that derives spatial coordinates.
172
+ * It does so by evaluating constraints of the form:
173
+ *
174
+ * A.x = 100 when A.T.hover = 1
175
+ * A.x = 0 when A.T.hover = 0
176
+ *
177
+ * This ensures the constraint graph is the single source of truth.
178
+ */
179
+ interface ConstraintSolver {
180
+ evaluate(mutations: TStateMutation[]): Map<EntityId, PVectorBounds>;
181
+ }
182
+ interface TopologyRounder {
183
+ round(bounds: Map<EntityId, PVectorBounds>): {
184
+ bounds: Map<EntityId, RasterBounds>;
185
+ violations: unknown[];
186
+ };
187
+ }
188
+ interface CanvasRenderer {
189
+ getEntity(id: EntityId): RenderableEntity | undefined;
190
+ draw(command: DrawCommand): void;
191
+ flush(): void;
192
+ }
193
+ interface DOMLayer {
194
+ getElement(id: EntityId): HTMLElement | undefined;
195
+ initializeProxyElement(id: EntityId, element: HTMLElement, bounds: RasterBounds): void;
196
+ }
197
+ /**
198
+ * Event buffer interface for async event atomicity.
199
+ *
200
+ * The render loop calls mergeAsyncEvents() at the start of each tick
201
+ * to integrate events from async callbacks (fetch, setTimeout, etc.)
202
+ * before processing sync events.
203
+ */
204
+ interface EventBufferInterface {
205
+ /**
206
+ * Merge pending async events into the main buffers.
207
+ *
208
+ * Called at tick start to ensure deterministic event ordering.
209
+ */
210
+ mergeAsyncEvents(): void;
211
+ }
212
+ /**
213
+ * Initialize a DOM element as a strict ViewScript proxy.
214
+ *
215
+ * This function:
216
+ * 1. Sets all required CSS properties for compositor-only updates
217
+ * 2. Wraps the style object in a Proxy to prevent layout-triggering mutations
218
+ * 3. Ensures the element is a "transparent tactile proxy" with no visual rendering
219
+ *
220
+ * ## Architect Directive: Zero Layout Engine Intervention
221
+ *
222
+ * The browser's layout engine (Reflow) must NEVER be invoked by DOM proxy
223
+ * elements. All positioning is done via GPU-accelerated `transform: translate3d()`.
224
+ */
225
+ export declare function initializeDOMProxyElement(element: HTMLElement, bounds: RasterBounds): void;
226
+ /**
227
+ * Update a DOM proxy element's transform (position).
228
+ *
229
+ * This is the ONLY way to change a proxy element's position after initialization.
230
+ * It uses compositor-only transform, avoiding layout recalculation.
231
+ */
232
+ export declare function updateDOMProxyTransform(element: HTMLElement, x: number, y: number): void;
233
+ export {};
234
+ /**
235
+ * ## tick() Control Flow (Pseudocode)
236
+ *
237
+ * ```
238
+ * function tick(timestamp):
239
+ * // 1. Backpressure: Take at most N mutations from queue
240
+ * mutations = pendingMutations.splice(0, MAX_PER_FRAME)
241
+ *
242
+ * // 2. P-dimension: Solve constraints with new T-vector values
243
+ * pBounds = constraintSolver.evaluate(mutations)
244
+ *
245
+ * // 3. Rasterize: Rational → Integer with topology preservation
246
+ * rBounds = topologyRounder.round(pBounds)
247
+ *
248
+ * // 4. Diff: Find entities that changed since last frame
249
+ * dirtySet = diff(previousBounds, rBounds)
250
+ *
251
+ * // 5. Canvas: Build draw commands for dirty entities only
252
+ * drawCmds = buildDrawCommands(dirtySet)
253
+ *
254
+ * // 6. DOM: Build transform mutations (no width/height!)
255
+ * domMuts = buildDOMMutations(dirtySet)
256
+ *
257
+ * // 7. ATOMIC COMMIT (no interleaved reads/writes)
258
+ * for cmd in drawCmds: canvasRenderer.draw(cmd)
259
+ * canvasRenderer.flush() // GPU sync point
260
+ * for mut in domMuts: mut.element.style.transform = mut.transform
261
+ *
262
+ * // 8. Swap buffers
263
+ * previousBounds = rBounds
264
+ *
265
+ * // 9. Schedule next frame
266
+ * requestAnimationFrame(tick)
267
+ * ```
268
+ *
269
+ * ## Reflow Prevention Strategy
270
+ *
271
+ * 1. DOM elements are created once with fixed dimensions (via CSS)
272
+ * 2. Position changes use ONLY transform: translate3d()
273
+ * 3. translate3d() is compositor-only (GPU, no layout recalc)
274
+ * 4. will-change: transform hints browser to promote layer
275
+ * 5. All style writes happen before any reads (no thrashing)
276
+ * 6. Size changes require re-creating the element (rare)
277
+ */