summaryrefslogtreecommitdiff
path: root/tools/timeline_editor/test_format.html
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-17 07:52:48 +0100
committerskal <pascal.massimino@gmail.com>2026-02-17 07:52:48 +0100
commitcd53ff0be8971b592d8d01836a6572c4123e5495 (patch)
treeaa0f9e48a906e352b6c3ab198580ff48978cc2da /tools/timeline_editor/test_format.html
parentcdd14146df16de0493acfd6dfbf24c154edbfce3 (diff)
feat: add Sequence V2 format support to timeline editor
Implements full support for the sequence v2 DAG format with explicit node routing and arrow syntax. New features: - timeline-format.js module for parsing/serializing v2 format - NODE declarations with typed buffers (u8x4_norm, f32x4, etc.) - Arrow syntax for effect routing: input1 input2 -> output1 output2 - Buffer chain visualization in properties panel and tooltips - Node editor modal for adding/deleting node declarations - Validation for undeclared node references (when NODEs explicit) - Backward compatible with auto-inferred nodes Files added: - tools/timeline_editor/timeline-format.js (214 lines) - tools/timeline_editor/test_format.html (automated tests) - workspaces/test/timeline_v2_test.seq (test file with NODE declarations) Files modified: - tools/timeline_editor/index.html (~40 changes for v2 support) All success criteria met. Round-trip tested with existing timelines. handoff(Claude): Timeline editor now fully supports v2 format with explicit node routing, NODE declarations, and buffer chain visualization. Parser handles both explicit NODE declarations and auto-inferred nodes. Validation only runs when explicit NODEs exist. Ready for production use. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/timeline_editor/test_format.html')
-rw-r--r--tools/timeline_editor/test_format.html127
1 files changed, 127 insertions, 0 deletions
diff --git a/tools/timeline_editor/test_format.html b/tools/timeline_editor/test_format.html
new file mode 100644
index 0000000..12b788f
--- /dev/null
+++ b/tools/timeline_editor/test_format.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Timeline Format Test</title>
+ <style>
+ body { font-family: monospace; padding: 20px; background: #1e1e1e; color: #d4d4d4; }
+ .test { margin: 20px 0; padding: 10px; border: 1px solid #444; }
+ .pass { border-color: #4ec9b0; }
+ .fail { border-color: #f48771; }
+ pre { background: #2d2d30; padding: 10px; overflow-x: auto; }
+ </style>
+</head>
+<body>
+ <h1>Timeline Format V2 Test</h1>
+ <div id="results"></div>
+
+ <script type="module">
+ import { TimelineFormat } from './timeline-format.js';
+
+ const timelineFormat = new TimelineFormat();
+ const results = document.getElementById('results');
+
+ function addTest(name, passed, details) {
+ const div = document.createElement('div');
+ div.className = `test ${passed ? 'pass' : 'fail'}`;
+ div.innerHTML = `<h3>${passed ? '✓' : '✗'} ${name}</h3>${details}`;
+ results.appendChild(div);
+ }
+
+ // Test 1: Parse existing timeline (no NODE declarations)
+ const testTimeline1 = `# WORKSPACE: test
+# BPM 120
+
+SEQUENCE 0.0 0 "MainLoop"
+EFFECT + Hybrid3D source -> temp1 0.00 4.00
+EFFECT + GaussianBlur temp1 -> sink 0.00 4.00`;
+
+ const parsed1 = timelineFormat.parse(testTimeline1, 120);
+ const test1Pass = parsed1.sequences.length === 1 &&
+ parsed1.sequences[0].effects.length === 2 &&
+ parsed1.sequences[0].effects[0].inputs[0] === 'source' &&
+ parsed1.sequences[0].effects[0].outputs[0] === 'temp1';
+ addTest('Parse timeline without NODE declarations', test1Pass,
+ `<pre>Sequences: ${parsed1.sequences.length}
+Effects: ${parsed1.sequences[0].effects.length}
+First effect inputs: ${parsed1.sequences[0].effects[0].inputs.join(', ')}
+First effect outputs: ${parsed1.sequences[0].effects[0].outputs.join(', ')}</pre>`);
+
+ // Test 2: Parse timeline with NODE declarations
+ const testTimeline2 = `# BPM 120
+SEQUENCE 0.0 0 "v2_test"
+ NODE temp1 u8x4_norm
+ NODE temp2 f32x4
+ EFFECT + Hybrid3D source -> temp1 0.0 4.0
+ EFFECT + GaussianBlur temp1 -> temp2 0.0 4.0`;
+
+ const parsed2 = timelineFormat.parse(testTimeline2, 120);
+ const test2Pass = parsed2.sequences[0].nodes.length === 2 &&
+ parsed2.sequences[0].nodes[0].name === 'temp1' &&
+ parsed2.sequences[0].nodes[0].type === 'u8x4_norm';
+ addTest('Parse timeline with NODE declarations', test2Pass,
+ `<pre>Nodes: ${parsed2.sequences[0].nodes.length}
+Node 1: ${parsed2.sequences[0].nodes[0].name} (${parsed2.sequences[0].nodes[0].type})
+Node 2: ${parsed2.sequences[0].nodes[1].name} (${parsed2.sequences[0].nodes[1].type})</pre>`);
+
+ // Test 3: Serialize without nodes
+ const serialized1 = timelineFormat.serialize(parsed1.sequences, 120);
+ const test3Pass = serialized1.includes('EFFECT + Hybrid3D source -> temp1') &&
+ !serialized1.includes('NODE');
+ addTest('Serialize timeline without NODE declarations', test3Pass,
+ `<pre>${serialized1}</pre>`);
+
+ // Test 4: Serialize with nodes
+ const serialized2 = timelineFormat.serialize(parsed2.sequences, 120);
+ const test4Pass = serialized2.includes('NODE temp1 u8x4_norm') &&
+ serialized2.includes('NODE temp2 f32x4');
+ addTest('Serialize timeline with NODE declarations', test4Pass,
+ `<pre>${serialized2}</pre>`);
+
+ // Test 5: Validation (no nodes - should pass)
+ const errors1 = timelineFormat.validateSequence(parsed1.sequences[0]);
+ const test5Pass = errors1.length === 0;
+ addTest('Validation without NODE declarations', test5Pass,
+ `<pre>Errors: ${errors1.length}</pre>`);
+
+ // Test 6: Validation (with nodes - should pass)
+ const errors2 = timelineFormat.validateSequence(parsed2.sequences[0]);
+ const test6Pass = errors2.length === 0;
+ addTest('Validation with NODE declarations', test6Pass,
+ `<pre>Errors: ${errors2.length}</pre>`);
+
+ // Test 7: Validation failure (undeclared node)
+ const invalidSeq = {
+ nodes: [{ name: 'temp1', type: 'u8x4_norm' }],
+ effects: [{
+ className: 'Test',
+ inputs: ['source'],
+ outputs: ['temp2'], // temp2 not declared!
+ startTime: 0,
+ endTime: 4
+ }]
+ };
+ const errors3 = timelineFormat.validateSequence(invalidSeq);
+ const test7Pass = errors3.length > 0 && errors3[0].includes('temp2');
+ addTest('Validation detects undeclared nodes', test7Pass,
+ `<pre>Errors: ${errors3.join('\n')}</pre>`);
+
+ // Test 8: Round-trip (parse → serialize → parse)
+ const reparsed = timelineFormat.parse(serialized2, 120);
+ const test8Pass = JSON.stringify(parsed2.sequences[0].nodes) ===
+ JSON.stringify(reparsed.sequences[0].nodes);
+ addTest('Round-trip consistency', test8Pass,
+ `<pre>Original nodes: ${JSON.stringify(parsed2.sequences[0].nodes, null, 2)}
+Reparsed nodes: ${JSON.stringify(reparsed.sequences[0].nodes, null, 2)}</pre>`);
+
+ // Summary
+ const allTests = document.querySelectorAll('.test');
+ const passed = document.querySelectorAll('.test.pass').length;
+ const total = allTests.length;
+ const summary = document.createElement('div');
+ summary.style.cssText = 'margin-top: 30px; padding: 20px; background: #2d2d30; font-size: 18px;';
+ summary.innerHTML = `<strong>Results: ${passed}/${total} tests passed</strong>`;
+ results.appendChild(summary);
+ </script>
+</body>
+</html>