@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.
- package/dist/ast/types.d.ts +403 -0
- package/dist/ast/types.js +33 -0
- package/dist/compiler/chunk-splitter.d.ts +98 -0
- package/dist/compiler/chunk-splitter.js +361 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +17 -0
- package/dist/rasterizer/__tests__/error-distribution.test.d.ts +7 -0
- package/dist/rasterizer/__tests__/error-distribution.test.js +322 -0
- package/dist/rasterizer/canvas-mapper.d.ts +280 -0
- package/dist/rasterizer/canvas-mapper.js +414 -0
- package/dist/rasterizer/error-distribution.d.ts +143 -0
- package/dist/rasterizer/error-distribution.js +231 -0
- package/dist/rasterizer/gradient-mapper.d.ts +223 -0
- package/dist/rasterizer/gradient-mapper.js +352 -0
- package/dist/rasterizer/topology-rounding.d.ts +151 -0
- package/dist/rasterizer/topology-rounding.js +347 -0
- package/dist/runtime/__tests__/event-backpressure.test.d.ts +10 -0
- package/dist/runtime/__tests__/event-backpressure.test.js +190 -0
- package/dist/runtime/event-backpressure.d.ts +393 -0
- package/dist/runtime/event-backpressure.js +458 -0
- package/dist/runtime/render-loop.d.ts +277 -0
- package/dist/runtime/render-loop.js +435 -0
- package/dist/runtime/wasm-resource-manager.d.ts +122 -0
- package/dist/runtime/wasm-resource-manager.js +253 -0
- package/dist/runtime/wgpu-renderer-adapter.d.ts +168 -0
- package/dist/runtime/wgpu-renderer-adapter.js +230 -0
- package/dist/semantic/__tests__/semantic-translator.test.d.ts +4 -0
- package/dist/semantic/__tests__/semantic-translator.test.js +203 -0
- package/dist/semantic/semantic-translator.d.ts +229 -0
- package/dist/semantic/semantic-translator.js +398 -0
- package/package.json +28 -0
- package/playwright-report/data/0bafe4e0863f0e244bba68a838f73241f8f2efaa.md +226 -0
- package/playwright-report/data/9281aca8abfb06c6cecb35d5ddd13d61f8c752d8.md +226 -0
- package/playwright-report/index.html +90 -0
- package/playwright.config.ts +160 -0
- package/screenshot-chrome.png +0 -0
- package/screenshots/visual-demo-verification.png +0 -0
- package/screenshots/visual-demo.png +0 -0
- package/src/ast/types.ts +473 -0
- package/src/compiler/chunk-splitter.ts +534 -0
- package/src/index.ts +62 -0
- package/src/rasterizer/__tests__/error-distribution.test.ts +382 -0
- package/src/rasterizer/canvas-mapper.ts +677 -0
- package/src/rasterizer/error-distribution.ts +344 -0
- package/src/rasterizer/gradient-mapper.ts +563 -0
- package/src/rasterizer/topology-rounding.ts +499 -0
- package/src/runtime/__tests__/event-backpressure.test.ts +254 -0
- package/src/runtime/event-backpressure.ts +622 -0
- package/src/runtime/render-loop.ts +660 -0
- package/src/runtime/wasm-resource-manager.ts +349 -0
- package/src/runtime/wgpu-renderer-adapter.ts +318 -0
- package/src/semantic/__tests__/semantic-translator.test.ts +263 -0
- package/src/semantic/semantic-translator.ts +637 -0
- package/test-results/.last-run.json +4 -0
- package/tests/e2e/async-race.spec.ts +612 -0
- package/tests/e2e/bilayer-sync.spec.ts +405 -0
- package/tests/e2e/failures/.gitkeep +0 -0
- package/tests/e2e/fullstack.spec.ts +681 -0
- package/tests/e2e/g1-continuity.spec.ts +703 -0
- package/tests/e2e/golden/.gitkeep +0 -0
- package/tests/e2e/golden/conic-color-wheel.raw +0 -0
- package/tests/e2e/golden/conic-color-wheel.sha256 +1 -0
- package/tests/e2e/golden/conic-rotated.raw +0 -0
- package/tests/e2e/golden/conic-rotated.sha256 +1 -0
- package/tests/e2e/golden/linear-45deg.raw +0 -0
- package/tests/e2e/golden/linear-45deg.sha256 +1 -0
- package/tests/e2e/golden/linear-horizontal.raw +0 -0
- package/tests/e2e/golden/linear-horizontal.sha256 +1 -0
- package/tests/e2e/golden/linear-multi-stop.raw +0 -0
- package/tests/e2e/golden/linear-multi-stop.sha256 +1 -0
- package/tests/e2e/golden/radial-circle-center.raw +0 -0
- package/tests/e2e/golden/radial-circle-center.sha256 +1 -0
- package/tests/e2e/golden/radial-offset.raw +0 -0
- package/tests/e2e/golden/radial-offset.sha256 +1 -0
- package/tests/e2e/golden/tile-mirror.raw +0 -0
- package/tests/e2e/golden/tile-mirror.sha256 +1 -0
- package/tests/e2e/golden/tile-repeat.raw +0 -0
- package/tests/e2e/golden/tile-repeat.sha256 +1 -0
- package/tests/e2e/gradient-animation.spec.ts +606 -0
- package/tests/e2e/memory-stability.spec.ts +396 -0
- package/tests/e2e/path-topology.spec.ts +674 -0
- package/tests/e2e/performance-profile.spec.ts +501 -0
- package/tests/e2e/screenshot.spec.ts +60 -0
- package/tests/e2e/test-harness.html +1005 -0
- package/tests/e2e/text-layout.spec.ts +451 -0
- package/tests/e2e/visual-demo.html +340 -0
- package/tests/e2e/visual-regression.spec.ts +335 -0
- package/tsconfig.json +12 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Playwright Configuration for ViewScript Renderer E2E Tests
|
|
5
|
+
*
|
|
6
|
+
* ## Determinism Strategy
|
|
7
|
+
*
|
|
8
|
+
* 1. Single browser (Chromium) for consistency
|
|
9
|
+
* 2. Fixed viewport size (800x600)
|
|
10
|
+
* 3. Disable GPU acceleration for visual tests
|
|
11
|
+
* 4. Disable animations for timing predictability
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
testDir: './tests/e2e',
|
|
16
|
+
fullyParallel: false, // Serial execution for determinism
|
|
17
|
+
forbidOnly: !!process.env.CI,
|
|
18
|
+
retries: process.env.CI ? 2 : 0,
|
|
19
|
+
workers: 1, // Single worker for determinism
|
|
20
|
+
reporter: [
|
|
21
|
+
['html', { open: 'never' }],
|
|
22
|
+
['json', { outputFile: 'test-results/results.json' }],
|
|
23
|
+
],
|
|
24
|
+
use: {
|
|
25
|
+
baseURL: 'http://localhost:3000',
|
|
26
|
+
trace: 'on-first-retry',
|
|
27
|
+
video: 'on-first-retry',
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
projects: [
|
|
31
|
+
{
|
|
32
|
+
name: 'visual-regression',
|
|
33
|
+
testMatch: /visual-regression\.spec\.ts/,
|
|
34
|
+
use: {
|
|
35
|
+
...devices['Desktop Chrome'],
|
|
36
|
+
viewport: { width: 800, height: 600 },
|
|
37
|
+
deviceScaleFactor: 1, // Fixed DPI
|
|
38
|
+
// Force software rendering
|
|
39
|
+
launchOptions: {
|
|
40
|
+
args: [
|
|
41
|
+
'--disable-gpu',
|
|
42
|
+
'--disable-gpu-compositing',
|
|
43
|
+
'--disable-gpu-rasterization',
|
|
44
|
+
'--disable-software-rasterizer',
|
|
45
|
+
'--use-gl=swiftshader',
|
|
46
|
+
'--disable-accelerated-2d-canvas',
|
|
47
|
+
'--disable-accelerated-video-decode',
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'bilayer-sync',
|
|
54
|
+
testMatch: /bilayer-sync\.spec\.ts/,
|
|
55
|
+
use: {
|
|
56
|
+
...devices['Desktop Chrome'],
|
|
57
|
+
viewport: { width: 800, height: 600 },
|
|
58
|
+
deviceScaleFactor: 1, // Fixed DPI for coordinate determinism
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'performance',
|
|
63
|
+
testMatch: /performance-profile\.spec\.ts/,
|
|
64
|
+
use: {
|
|
65
|
+
...devices['Desktop Chrome'],
|
|
66
|
+
viewport: { width: 800, height: 600 },
|
|
67
|
+
// Enable GPU for realistic perf testing
|
|
68
|
+
launchOptions: {
|
|
69
|
+
args: [
|
|
70
|
+
'--enable-gpu-rasterization',
|
|
71
|
+
'--enable-zero-copy',
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'memory-stability',
|
|
78
|
+
testMatch: /memory-stability\.spec\.ts/,
|
|
79
|
+
use: {
|
|
80
|
+
...devices['Desktop Chrome'],
|
|
81
|
+
viewport: { width: 800, height: 600 },
|
|
82
|
+
},
|
|
83
|
+
timeout: 300000, // 5 minutes for stress test
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'async-race',
|
|
87
|
+
testMatch: /async-race\.spec\.ts/,
|
|
88
|
+
use: {
|
|
89
|
+
...devices['Desktop Chrome'],
|
|
90
|
+
viewport: { width: 800, height: 600 },
|
|
91
|
+
deviceScaleFactor: 1, // Fixed DPI for coordinate determinism
|
|
92
|
+
},
|
|
93
|
+
timeout: 60000, // 1 minute for event storm processing
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'screenshot',
|
|
97
|
+
testMatch: /screenshot\.spec\.ts/,
|
|
98
|
+
use: {
|
|
99
|
+
...devices['Desktop Chrome'],
|
|
100
|
+
viewport: { width: 800, height: 600 },
|
|
101
|
+
deviceScaleFactor: 1,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'path-topology',
|
|
106
|
+
testMatch: /path-topology\.spec\.ts/,
|
|
107
|
+
use: {
|
|
108
|
+
...devices['Desktop Chrome'],
|
|
109
|
+
viewport: { width: 800, height: 600 },
|
|
110
|
+
deviceScaleFactor: 1, // Fixed DPI for pixel-perfect topology verification
|
|
111
|
+
},
|
|
112
|
+
timeout: 30000,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'g1-continuity',
|
|
116
|
+
testMatch: /g1-continuity\.spec\.ts/,
|
|
117
|
+
use: {
|
|
118
|
+
...devices['Desktop Chrome'],
|
|
119
|
+
viewport: { width: 800, height: 600 },
|
|
120
|
+
deviceScaleFactor: 1, // Fixed DPI for tangent smoothness verification
|
|
121
|
+
},
|
|
122
|
+
timeout: 30000,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'fullstack',
|
|
126
|
+
testMatch: /fullstack\.spec\.ts/,
|
|
127
|
+
use: {
|
|
128
|
+
...devices['Desktop Chrome'],
|
|
129
|
+
viewport: { width: 800, height: 600 },
|
|
130
|
+
deviceScaleFactor: 1, // Fixed DPI for coordinate determinism
|
|
131
|
+
},
|
|
132
|
+
timeout: 60000, // 1 minute for full pipeline tests
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'text-layout',
|
|
136
|
+
testMatch: /text-layout\.spec\.ts/,
|
|
137
|
+
use: {
|
|
138
|
+
...devices['Desktop Chrome'],
|
|
139
|
+
viewport: { width: 800, height: 600 },
|
|
140
|
+
deviceScaleFactor: 1,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'gradient-animation',
|
|
145
|
+
testMatch: /gradient-animation\.spec\.ts/,
|
|
146
|
+
use: {
|
|
147
|
+
...devices['Desktop Chrome'],
|
|
148
|
+
viewport: { width: 800, height: 600 },
|
|
149
|
+
deviceScaleFactor: 1,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
|
|
154
|
+
webServer: {
|
|
155
|
+
command: 'npx serve tests/e2e -l 3000 --no-clipboard',
|
|
156
|
+
url: 'http://localhost:3000/test-harness.html',
|
|
157
|
+
reuseExistingServer: !process.env.CI,
|
|
158
|
+
timeout: 30000,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/ast/types.ts
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViewScript Renderer AST: Bilayer Orthogonal Architecture
|
|
3
|
+
*
|
|
4
|
+
* This module defines the type system for the dual-layer rendering model:
|
|
5
|
+
* - Canvas Layer: Visual representation (wgpu draw commands)
|
|
6
|
+
* - DOM Layer: Interaction regions (accessibility, events, focus)
|
|
7
|
+
*
|
|
8
|
+
* ## Architectural Invariants
|
|
9
|
+
*
|
|
10
|
+
* 1. Every logical entity has exactly one EntityId
|
|
11
|
+
* 2. An entity MAY have a CanvasNode (visual), a DOMNode (interactive), or both
|
|
12
|
+
* 3. Canvas and DOM nodes are synchronized via shared EntityId
|
|
13
|
+
* 4. DOM nodes have NO visual properties; Canvas nodes have NO interaction logic
|
|
14
|
+
*
|
|
15
|
+
* ## Data Flow
|
|
16
|
+
*
|
|
17
|
+
* ```
|
|
18
|
+
* IR (.vs) Renderer AST
|
|
19
|
+
* ────────────────────────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* ┌─────────────┐ ┌─────────────────────────────┐
|
|
22
|
+
* │ Constraint │ │ RenderableEntity │
|
|
23
|
+
* │ Graph │ ──▶ │ ┌─────────┐ ┌───────────┐ │
|
|
24
|
+
* │ (P-dim) │ │ │ Canvas │ │ DOM │ │
|
|
25
|
+
* └─────────────┘ │ │ Node │ │ Node │ │
|
|
26
|
+
* │ └─────────┘ └───────────┘ │
|
|
27
|
+
* │ ▲ ▲ │
|
|
28
|
+
* │ └─────┬─────┘ │
|
|
29
|
+
* │ EntityId │
|
|
30
|
+
* └─────────────────────────────┘
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Core Identity Types
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/** Unique identifier for all P-dimension entities. */
|
|
39
|
+
export type EntityId = number;
|
|
40
|
+
|
|
41
|
+
/** Unique identifier for constraints. */
|
|
42
|
+
export type ConstraintId = number;
|
|
43
|
+
|
|
44
|
+
/** Unique identifier for render chunks (for progressive loading). */
|
|
45
|
+
export type ChunkId = string;
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Coordinate Types (Pre-Rasterization)
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Rational number representation for exact arithmetic.
|
|
53
|
+
* Used before rasterization to pixel coordinates.
|
|
54
|
+
*/
|
|
55
|
+
export interface Rational {
|
|
56
|
+
numerator: bigint;
|
|
57
|
+
denominator: bigint;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* P-dimension vector with exact rational coordinates.
|
|
62
|
+
* T is included for animation/state-dependent positioning.
|
|
63
|
+
*/
|
|
64
|
+
export interface PVector {
|
|
65
|
+
x: Rational;
|
|
66
|
+
y: Rational;
|
|
67
|
+
z: Rational; // Layering order (not depth in 3D sense)
|
|
68
|
+
t: Rational; // Time/state parameter
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Rasterized coordinates for actual rendering.
|
|
73
|
+
* Produced by the topology-preserving rounding algorithm.
|
|
74
|
+
*/
|
|
75
|
+
export interface RasterCoord {
|
|
76
|
+
/** X in device pixels */
|
|
77
|
+
x: number;
|
|
78
|
+
/** Y in device pixels */
|
|
79
|
+
y: number;
|
|
80
|
+
/** Z-index for layering */
|
|
81
|
+
zIndex: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Canvas Layer Types (Visual Representation)
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Base interface for all Canvas layer nodes.
|
|
90
|
+
* These produce wgpu draw commands.
|
|
91
|
+
*/
|
|
92
|
+
export interface CanvasNodeBase {
|
|
93
|
+
/** Discriminant for node type */
|
|
94
|
+
readonly kind: string;
|
|
95
|
+
|
|
96
|
+
/** Back-reference to logical entity */
|
|
97
|
+
entityId: EntityId;
|
|
98
|
+
|
|
99
|
+
/** Pre-rasterization coordinates */
|
|
100
|
+
bounds: PVectorBounds;
|
|
101
|
+
|
|
102
|
+
/** Rasterized coordinates (computed by rounding algorithm) */
|
|
103
|
+
rasterBounds: RasterBounds;
|
|
104
|
+
|
|
105
|
+
/** Z-order for painter's algorithm */
|
|
106
|
+
zOrder: number;
|
|
107
|
+
|
|
108
|
+
/** Chunk this node belongs to (for progressive loading) */
|
|
109
|
+
chunkId: ChunkId;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface PVectorBounds {
|
|
113
|
+
topLeft: PVector;
|
|
114
|
+
bottomRight: PVector;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface RasterBounds {
|
|
118
|
+
x: number;
|
|
119
|
+
y: number;
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Path-based canvas node (curves, shapes).
|
|
126
|
+
*/
|
|
127
|
+
export interface CanvasPathNode extends CanvasNodeBase {
|
|
128
|
+
kind: 'path';
|
|
129
|
+
|
|
130
|
+
/** SVG-like path commands, pre-compiled */
|
|
131
|
+
pathData: PathCommand[];
|
|
132
|
+
|
|
133
|
+
/** Fill style (solid, gradient, pattern) */
|
|
134
|
+
fill: FillStyle | null;
|
|
135
|
+
|
|
136
|
+
/** Stroke style */
|
|
137
|
+
stroke: StrokeStyle | null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type PathCommand =
|
|
141
|
+
| { type: 'M'; x: Rational; y: Rational }
|
|
142
|
+
| { type: 'L'; x: Rational; y: Rational }
|
|
143
|
+
| { type: 'C'; x1: Rational; y1: Rational; x2: Rational; y2: Rational; x: Rational; y: Rational }
|
|
144
|
+
| { type: 'Q'; x1: Rational; y1: Rational; x: Rational; y: Rational }
|
|
145
|
+
| { type: 'A'; rx: Rational; ry: Rational; rotation: number; largeArc: boolean; sweep: boolean; x: Rational; y: Rational }
|
|
146
|
+
| { type: 'Z' };
|
|
147
|
+
|
|
148
|
+
export interface FillStyle {
|
|
149
|
+
type: 'solid' | 'linear-gradient' | 'radial-gradient' | 'pattern';
|
|
150
|
+
color?: string; // For solid
|
|
151
|
+
stops?: GradientStop[]; // For gradients
|
|
152
|
+
patternRef?: EntityId; // For patterns
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface GradientStop {
|
|
156
|
+
offset: Rational; // 0-1
|
|
157
|
+
color: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface StrokeStyle {
|
|
161
|
+
color: string;
|
|
162
|
+
width: Rational;
|
|
163
|
+
lineCap: 'butt' | 'round' | 'square';
|
|
164
|
+
lineJoin: 'miter' | 'round' | 'bevel';
|
|
165
|
+
dashArray?: Rational[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Text canvas node.
|
|
170
|
+
*/
|
|
171
|
+
export interface CanvasTextNode extends CanvasNodeBase {
|
|
172
|
+
kind: 'text';
|
|
173
|
+
|
|
174
|
+
/** Text content (may be Q-dimension bound) */
|
|
175
|
+
content: string | QDimensionRef;
|
|
176
|
+
|
|
177
|
+
/** Font specification */
|
|
178
|
+
font: FontSpec;
|
|
179
|
+
|
|
180
|
+
/** Text layout results (computed) */
|
|
181
|
+
glyphs: GlyphRun[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface FontSpec {
|
|
185
|
+
family: string;
|
|
186
|
+
size: Rational;
|
|
187
|
+
weight: number;
|
|
188
|
+
style: 'normal' | 'italic' | 'oblique';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface GlyphRun {
|
|
192
|
+
glyphIds: number[];
|
|
193
|
+
positions: RasterCoord[];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Image canvas node (Q-dimension source).
|
|
198
|
+
*/
|
|
199
|
+
export interface CanvasImageNode extends CanvasNodeBase {
|
|
200
|
+
kind: 'image';
|
|
201
|
+
|
|
202
|
+
/** Reference to Q-dimension image source */
|
|
203
|
+
source: QDimensionRef;
|
|
204
|
+
|
|
205
|
+
/** How to fit the image in bounds */
|
|
206
|
+
fit: 'fill' | 'contain' | 'cover' | 'none';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Group node for hierarchical transforms.
|
|
211
|
+
*/
|
|
212
|
+
export interface CanvasGroupNode extends CanvasNodeBase {
|
|
213
|
+
kind: 'group';
|
|
214
|
+
|
|
215
|
+
/** Child nodes */
|
|
216
|
+
children: CanvasNode[];
|
|
217
|
+
|
|
218
|
+
/** Transform matrix (2D affine) */
|
|
219
|
+
transform: AffineTransform;
|
|
220
|
+
|
|
221
|
+
/** Clip path (optional) */
|
|
222
|
+
clipPath?: PathCommand[];
|
|
223
|
+
|
|
224
|
+
/** Opacity (0-1) */
|
|
225
|
+
opacity: number;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface AffineTransform {
|
|
229
|
+
a: number; b: number;
|
|
230
|
+
c: number; d: number;
|
|
231
|
+
tx: number; ty: number;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export type CanvasNode =
|
|
235
|
+
| CanvasPathNode
|
|
236
|
+
| CanvasTextNode
|
|
237
|
+
| CanvasImageNode
|
|
238
|
+
| CanvasGroupNode;
|
|
239
|
+
|
|
240
|
+
// =============================================================================
|
|
241
|
+
// DOM Layer Types (Interaction Regions)
|
|
242
|
+
// =============================================================================
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Base interface for all DOM layer nodes.
|
|
246
|
+
* These produce invisible DOM elements for interaction.
|
|
247
|
+
*/
|
|
248
|
+
export interface DOMNodeBase {
|
|
249
|
+
/** Discriminant for node type */
|
|
250
|
+
readonly kind: string;
|
|
251
|
+
|
|
252
|
+
/** Back-reference to logical entity (MUST match CanvasNode if paired) */
|
|
253
|
+
entityId: EntityId;
|
|
254
|
+
|
|
255
|
+
/** Position synchronized with Canvas layer */
|
|
256
|
+
rasterBounds: RasterBounds;
|
|
257
|
+
|
|
258
|
+
/** ARIA attributes for accessibility */
|
|
259
|
+
aria: ARIAAttributes;
|
|
260
|
+
|
|
261
|
+
/** Tab index for keyboard navigation (-1 = not focusable) */
|
|
262
|
+
tabIndex: number;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface ARIAAttributes {
|
|
266
|
+
role?: string;
|
|
267
|
+
label?: string;
|
|
268
|
+
describedBy?: EntityId;
|
|
269
|
+
labelledBy?: EntityId;
|
|
270
|
+
hidden?: boolean;
|
|
271
|
+
expanded?: boolean;
|
|
272
|
+
selected?: boolean;
|
|
273
|
+
checked?: boolean | 'mixed';
|
|
274
|
+
disabled?: boolean;
|
|
275
|
+
live?: 'off' | 'polite' | 'assertive';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Interactive region (clickable, focusable).
|
|
280
|
+
*/
|
|
281
|
+
export interface DOMInteractiveNode extends DOMNodeBase {
|
|
282
|
+
kind: 'interactive';
|
|
283
|
+
|
|
284
|
+
/** Event bindings (Q-dimension triggers) */
|
|
285
|
+
events: EventBinding[];
|
|
286
|
+
|
|
287
|
+
/** Cursor style when hovering */
|
|
288
|
+
cursor: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export interface EventBinding {
|
|
292
|
+
/** DOM event type */
|
|
293
|
+
type: 'click' | 'pointerdown' | 'pointerup' | 'pointermove' | 'focus' | 'blur' | 'keydown' | 'keyup';
|
|
294
|
+
|
|
295
|
+
/** Constraint to update on event (T-vector mutation) */
|
|
296
|
+
targetConstraint: ConstraintId;
|
|
297
|
+
|
|
298
|
+
/** How to compute the new value */
|
|
299
|
+
valueMapping: EventValueMapping;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export type EventValueMapping =
|
|
303
|
+
| { type: 'constant'; value: Rational }
|
|
304
|
+
| { type: 'toggle'; values: [Rational, Rational] }
|
|
305
|
+
| { type: 'increment'; delta: Rational }
|
|
306
|
+
| { type: 'pointer-x' }
|
|
307
|
+
| { type: 'pointer-y' }
|
|
308
|
+
| { type: 'pointer-delta-x' }
|
|
309
|
+
| { type: 'pointer-delta-y' };
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Text input region.
|
|
313
|
+
*/
|
|
314
|
+
export interface DOMInputNode extends DOMNodeBase {
|
|
315
|
+
kind: 'input';
|
|
316
|
+
|
|
317
|
+
/** Input type */
|
|
318
|
+
inputType: 'text' | 'number' | 'password' | 'email' | 'search';
|
|
319
|
+
|
|
320
|
+
/** Constraint bound to input value */
|
|
321
|
+
valueConstraint: ConstraintId;
|
|
322
|
+
|
|
323
|
+
/** Placeholder text */
|
|
324
|
+
placeholder?: string;
|
|
325
|
+
|
|
326
|
+
/** Max length */
|
|
327
|
+
maxLength?: number;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Scroll container region.
|
|
332
|
+
*/
|
|
333
|
+
export interface DOMScrollNode extends DOMNodeBase {
|
|
334
|
+
kind: 'scroll';
|
|
335
|
+
|
|
336
|
+
/** Scroll direction */
|
|
337
|
+
direction: 'horizontal' | 'vertical' | 'both';
|
|
338
|
+
|
|
339
|
+
/** Constraints bound to scroll position */
|
|
340
|
+
scrollXConstraint?: ConstraintId;
|
|
341
|
+
scrollYConstraint?: ConstraintId;
|
|
342
|
+
|
|
343
|
+
/** Content size (for scrollbar calculation) */
|
|
344
|
+
contentSize: { width: Rational; height: Rational };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Focus trap region (for modals, dialogs).
|
|
349
|
+
*/
|
|
350
|
+
export interface DOMFocusTrapNode extends DOMNodeBase {
|
|
351
|
+
kind: 'focus-trap';
|
|
352
|
+
|
|
353
|
+
/** First and last focusable children */
|
|
354
|
+
focusableChildren: EntityId[];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export type DOMNode =
|
|
358
|
+
| DOMInteractiveNode
|
|
359
|
+
| DOMInputNode
|
|
360
|
+
| DOMScrollNode
|
|
361
|
+
| DOMFocusTrapNode;
|
|
362
|
+
|
|
363
|
+
// =============================================================================
|
|
364
|
+
// Q-Dimension References
|
|
365
|
+
// =============================================================================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Reference to Q-dimension (unpredictable) data.
|
|
369
|
+
*/
|
|
370
|
+
export interface QDimensionRef {
|
|
371
|
+
/** Type of Q-dimension source */
|
|
372
|
+
type: 'user-input' | 'fetch' | 'image' | 'video' | 'audio' | 'shader' | 'time';
|
|
373
|
+
|
|
374
|
+
/** Source identifier */
|
|
375
|
+
sourceId: string;
|
|
376
|
+
|
|
377
|
+
/** Current value (runtime state, not in IR) */
|
|
378
|
+
currentValue?: unknown;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// =============================================================================
|
|
382
|
+
// Unified Renderable Entity
|
|
383
|
+
// =============================================================================
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* A complete renderable entity combining Canvas and DOM representations.
|
|
387
|
+
*
|
|
388
|
+
* ## Pairing Rules
|
|
389
|
+
*
|
|
390
|
+
* | Entity Type | Canvas Node | DOM Node |
|
|
391
|
+
* |-----------------|-------------|-------------|
|
|
392
|
+
* | Static shape | Required | None |
|
|
393
|
+
* | Button | Required | Required |
|
|
394
|
+
* | Text input | Required | Required |
|
|
395
|
+
* | Decorative img | Required | None |
|
|
396
|
+
* | Interactive img | Required | Required |
|
|
397
|
+
* | Scroll area | Optional | Required |
|
|
398
|
+
* | Focus trap | None | Required |
|
|
399
|
+
*/
|
|
400
|
+
export interface RenderableEntity {
|
|
401
|
+
/** Unique identifier */
|
|
402
|
+
id: EntityId;
|
|
403
|
+
|
|
404
|
+
/** Human-readable name (for debugging) */
|
|
405
|
+
name?: string;
|
|
406
|
+
|
|
407
|
+
/** Canvas layer representation (visual) */
|
|
408
|
+
canvas: CanvasNode | null;
|
|
409
|
+
|
|
410
|
+
/** DOM layer representation (interactive) */
|
|
411
|
+
dom: DOMNode | null;
|
|
412
|
+
|
|
413
|
+
/** Constraints that affect this entity */
|
|
414
|
+
dependentConstraints: ConstraintId[];
|
|
415
|
+
|
|
416
|
+
/** Entities that this entity references */
|
|
417
|
+
referencedEntities: EntityId[];
|
|
418
|
+
|
|
419
|
+
/** Chunk membership */
|
|
420
|
+
chunkId: ChunkId;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// =============================================================================
|
|
424
|
+
// Render Tree (Complete AST)
|
|
425
|
+
// =============================================================================
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* The complete render tree produced by the compiler.
|
|
429
|
+
*/
|
|
430
|
+
export interface RenderTree {
|
|
431
|
+
/** All renderable entities, indexed by EntityId */
|
|
432
|
+
entities: Map<EntityId, RenderableEntity>;
|
|
433
|
+
|
|
434
|
+
/** Root entity IDs (top-level elements) */
|
|
435
|
+
roots: EntityId[];
|
|
436
|
+
|
|
437
|
+
/** Chunk definitions for progressive loading */
|
|
438
|
+
chunks: Map<ChunkId, Chunk>;
|
|
439
|
+
|
|
440
|
+
/** Viewport configuration */
|
|
441
|
+
viewport: ViewportConfig;
|
|
442
|
+
|
|
443
|
+
/** Device pixel ratio for rasterization */
|
|
444
|
+
devicePixelRatio: number;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export interface Chunk {
|
|
448
|
+
id: ChunkId;
|
|
449
|
+
|
|
450
|
+
/** Entities in this chunk */
|
|
451
|
+
entityIds: EntityId[];
|
|
452
|
+
|
|
453
|
+
/** Dependencies on other chunks */
|
|
454
|
+
dependsOn: ChunkId[];
|
|
455
|
+
|
|
456
|
+
/** Is this the initial chunk? */
|
|
457
|
+
isInitial: boolean;
|
|
458
|
+
|
|
459
|
+
/** Trigger conditions for lazy loading */
|
|
460
|
+
loadTriggers: LoadTrigger[];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export type LoadTrigger =
|
|
464
|
+
| { type: 'immediate' }
|
|
465
|
+
| { type: 'viewport-intersect'; entityId: EntityId }
|
|
466
|
+
| { type: 'event'; eventType: string; targetEntity: EntityId }
|
|
467
|
+
| { type: 'constraint-change'; constraintId: ConstraintId };
|
|
468
|
+
|
|
469
|
+
export interface ViewportConfig {
|
|
470
|
+
width: Rational;
|
|
471
|
+
height: Rational;
|
|
472
|
+
unitsPerPixel: Rational;
|
|
473
|
+
}
|