tjs-lang 0.2.7 → 0.3.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.
Files changed (40) hide show
  1. package/demo/docs.json +32 -26
  2. package/demo/src/examples.ts +23 -83
  3. package/demo/src/playground-shared.ts +666 -0
  4. package/demo/src/tjs-playground.ts +65 -550
  5. package/demo/src/ts-examples.ts +5 -4
  6. package/demo/src/ts-playground.ts +50 -414
  7. package/dist/index.js +143 -160
  8. package/dist/index.js.map +12 -12
  9. package/dist/src/lang/emitters/js.d.ts +34 -2
  10. package/dist/src/lang/index.d.ts +1 -1
  11. package/dist/src/lang/types.d.ts +1 -1
  12. package/dist/src/types/Type.d.ts +3 -1
  13. package/dist/tjs-full.js +143 -160
  14. package/dist/tjs-full.js.map +12 -12
  15. package/dist/tjs-transpiler.js +122 -55
  16. package/dist/tjs-transpiler.js.map +9 -8
  17. package/dist/tjs-vm.js +14 -14
  18. package/dist/tjs-vm.js.map +5 -5
  19. package/docs/docs.json +792 -0
  20. package/docs/index.js +2652 -2835
  21. package/docs/index.js.map +11 -10
  22. package/editors/codemirror/ajs-language.ts +27 -1
  23. package/editors/codemirror/autocomplete.test.ts +3 -3
  24. package/package.json +1 -1
  25. package/src/lang/codegen.test.ts +11 -11
  26. package/src/lang/emitters/from-ts.ts +1 -1
  27. package/src/lang/emitters/js.ts +228 -4
  28. package/src/lang/index.ts +0 -3
  29. package/src/lang/inference.ts +40 -8
  30. package/src/lang/lang.test.ts +192 -35
  31. package/src/lang/roundtrip.test.ts +155 -0
  32. package/src/lang/runtime.ts +7 -0
  33. package/src/lang/types.ts +2 -0
  34. package/src/lang/typescript-syntax.test.ts +6 -4
  35. package/src/lang/wasm.test.ts +20 -0
  36. package/src/lang/wasm.ts +143 -0
  37. package/src/types/Type.test.ts +64 -0
  38. package/src/types/Type.ts +22 -1
  39. package/src/use-cases/transpiler-integration.test.ts +10 -10
  40. package/src/vm/atoms/batteries.ts +2 -0
@@ -23,14 +23,17 @@ import {
23
23
  // Available modules for autocomplete introspection
24
24
  // These are the actual runtime values that can be introspected
25
25
  import { codeMirror, CodeMirror } from '../../editors/codemirror/component'
26
- import {
27
- tjs,
28
- compileWasmBlocksForIframe,
29
- generateWasmInstantiationCode,
30
- type TJSTranspileOptions,
31
- } from '../../src/lang'
26
+ import { tjs, type TJSTranspileOptions } from '../../src/lang'
32
27
  import { generateDocsMarkdown } from './docs-utils'
33
28
  import { extractImports, generateImportMap, resolveImports } from './imports'
29
+ import {
30
+ buildIframeDoc,
31
+ createIframeMessageHandler,
32
+ renderConsoleMessages,
33
+ renderTestResults,
34
+ formatExecTime,
35
+ sharedPlaygroundStyles,
36
+ } from './playground-shared'
34
37
  import { ModuleStore, type ValidationResult } from './module-store'
35
38
  import {
36
39
  buildAutocompleteContext,
@@ -443,42 +446,11 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
443
446
  }
444
447
 
445
448
  private renderConsole() {
446
- // Parse messages for line references and make them clickable
447
- // Patterns: "at line X", "line X:", "Line X", ":X:" (line:col)
448
- const linePattern =
449
- /(?:at line |line |Line )(\d+)(?:[:,]?\s*(?:column |col )?(\d+))?|:(\d+):(\d+)/g
450
-
451
- const html = this.consoleMessages
452
- .map((msg) => {
453
- // Escape HTML
454
- const escaped = msg
455
- .replace(/&/g, '&amp;')
456
- .replace(/</g, '&lt;')
457
- .replace(/>/g, '&gt;')
458
-
459
- // Replace line references with clickable spans
460
- return escaped.replace(linePattern, (match, l1, c1, l2, c2) => {
461
- const line = l1 || l2
462
- const col = c1 || c2 || '1'
463
- return `<span class="clickable-line" data-line="${line}" data-col="${col}">${match}</span>`
464
- })
465
- })
466
- .join('\n')
467
-
468
- this.parts.console.innerHTML = html
469
- this.parts.console.scrollTop = this.parts.console.scrollHeight
470
-
471
- // Add click handlers
472
- this.parts.console.querySelectorAll('.clickable-line').forEach((el) => {
473
- el.addEventListener('click', (e) => {
474
- const target = e.currentTarget as HTMLElement
475
- const line = parseInt(target.dataset.line || '0', 10)
476
- const col = parseInt(target.dataset.col || '1', 10)
477
- if (line > 0) {
478
- this.goToSourceLine(line, col)
479
- }
480
- })
481
- })
449
+ renderConsoleMessages(
450
+ this.consoleMessages,
451
+ this.parts.console,
452
+ (line, col) => this.goToSourceLine(line, col)
453
+ )
482
454
  }
483
455
 
484
456
  // Build flag toggle handlers
@@ -553,10 +525,7 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
553
525
  // Update test results and status bar with timing
554
526
  const tests = result.testResults || []
555
527
  const failed = tests.filter((t: any) => !t.passed).length
556
- const timeStr =
557
- this.lastTranspileTime < 1
558
- ? `${(this.lastTranspileTime * 1000).toFixed(0)}μs`
559
- : `${this.lastTranspileTime.toFixed(2)}ms`
528
+ const timeStr = formatExecTime(this.lastTranspileTime)
560
529
  if (failed > 0) {
561
530
  this.parts.statusBar.textContent = `Transpiled in ${timeStr} with ${failed} test failure${
562
531
  failed > 1 ? 's' : ''
@@ -624,75 +593,13 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
624
593
 
625
594
  private updateTestResults(result: any) {
626
595
  const tests = result.testResults
627
- if (!tests || tests.length === 0) {
628
- this.parts.testsOutput.textContent = 'No tests defined'
629
- this.updateTestsTabLabel(0, 0)
630
- this.parts.tjsEditor.clearMarkers()
631
- return
632
- }
633
-
634
- const passed = tests.filter((t: any) => t.passed).length
635
- const failed = tests.filter((t: any) => !t.passed).length
636
-
637
- // Update tab label with indicator
596
+ const { passed, failed } = renderTestResults(
597
+ tests,
598
+ this.parts.testsOutput,
599
+ this.parts.tjsEditor,
600
+ (line) => this.goToSourceLine(line)
601
+ )
638
602
  this.updateTestsTabLabel(passed, failed)
639
-
640
- // Set gutter markers for failed tests
641
- const failedTests = tests.filter((t: any) => !t.passed && t.line)
642
- if (failedTests.length > 0) {
643
- this.parts.tjsEditor.setMarkers(
644
- failedTests.map((t: any) => ({
645
- line: t.line,
646
- message: t.error || t.description,
647
- severity: 'error' as const,
648
- }))
649
- )
650
- } else {
651
- this.parts.tjsEditor.clearMarkers()
652
- }
653
-
654
- let html = `<div class="test-summary">`
655
- html += `<strong>${passed} passed</strong>`
656
- if (failed > 0) {
657
- html += `, <strong class="test-failed">${failed} failed</strong>`
658
- }
659
- html += `</div><ul class="test-list">`
660
-
661
- for (const test of tests) {
662
- const icon = test.passed ? '✓' : '✗'
663
- const cls = test.passed ? 'test-pass' : 'test-fail'
664
- const sigBadge = test.isSignatureTest
665
- ? ' <span class="sig-badge">signature</span>'
666
- : ''
667
- const clickable =
668
- !test.passed && test.line ? ' class="clickable-error"' : ''
669
- const dataLine = test.line ? ` data-line="${test.line}"` : ''
670
- html += `<li class="${cls}"${dataLine}>${icon} ${test.description}${sigBadge}`
671
- if (!test.passed && test.error) {
672
- html += `<div${clickable}${dataLine} class="test-error${
673
- test.line ? ' clickable-error' : ''
674
- }">${test.error}</div>`
675
- }
676
- html += `</li>`
677
- }
678
- html += `</ul>`
679
-
680
- this.parts.testsOutput.innerHTML = html
681
-
682
- // Add click handlers for clickable errors
683
- this.parts.testsOutput
684
- .querySelectorAll('.clickable-error')
685
- .forEach((el) => {
686
- el.addEventListener('click', (e) => {
687
- const line = parseInt(
688
- (e.currentTarget as HTMLElement).dataset.line || '0',
689
- 10
690
- )
691
- if (line > 0) {
692
- this.goToSourceLine(line)
693
- }
694
- })
695
- })
696
603
  }
697
604
 
698
605
  private updateTestsTabLabel(passed: number, failed: number) {
@@ -1057,29 +964,25 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
1057
964
  return
1058
965
  }
1059
966
 
967
+ // Show JS output immediately after successful transpilation
968
+ this.parts.outputTabs.value = 0 // JS is first tab (index 0)
969
+
1060
970
  this.parts.statusBar.textContent = 'Running...'
1061
971
 
1062
- // Compile WASM blocks if present and WASM is enabled
1063
- let wasmInstantiationCode = ''
1064
972
  try {
1065
- const wasmBlocks = this.lastTranspileResult.wasmBlocks
1066
- if (wasmBlocks && wasmBlocks.length > 0) {
1067
- if (this.buildFlags.wasm) {
1068
- this.log(`Compiling ${wasmBlocks.length} WASM block(s)...`)
1069
- const wasmResult = await compileWasmBlocksForIframe(wasmBlocks)
1070
- if (wasmResult.compiled.length > 0) {
1071
- this.log(`WASM: ${wasmResult.compiled.length} compiled`)
1072
- wasmInstantiationCode = generateWasmInstantiationCode(
1073
- wasmResult.compiled
1074
- )
1075
- }
1076
- if (wasmResult.failed > 0) {
1077
- this.log(`WASM: ${wasmResult.failed} failed (using JS fallback)`)
973
+ // Log WASM compilation results (WASM is now compiled at transpile time)
974
+ const wasmCompiled = this.lastTranspileResult.wasmCompiled
975
+ if (wasmCompiled && wasmCompiled.length > 0) {
976
+ const success = wasmCompiled.filter((w) => w.success).length
977
+ const failed = wasmCompiled.filter((w) => !w.success).length
978
+ if (success > 0) {
979
+ this.log(`WASM: ${success} block(s) compiled at transpile time`)
980
+ }
981
+ if (failed > 0) {
982
+ this.log(`WASM: ${failed} failed (using JS fallback)`)
983
+ for (const w of wasmCompiled.filter((w) => !w.success)) {
984
+ this.log(` ${w.id}: ${w.error}`)
1078
985
  }
1079
- } else {
1080
- this.log(
1081
- `WASM disabled - using JS fallback for ${wasmBlocks.length} block(s)`
1082
- )
1083
986
  }
1084
987
  }
1085
988
 
@@ -1123,134 +1026,33 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
1123
1026
  )
1124
1027
 
1125
1028
  // Create a complete HTML document for the iframe
1126
- const iframeDoc = `<!DOCTYPE html>
1127
- <html>
1128
- <head>
1129
- <style>${cssContent}</style>
1130
- ${importMapScript}
1131
- </head>
1132
- <body>
1133
- ${htmlContent}
1134
- <!-- TJS Runtime stub must be set up BEFORE imports execute -->
1135
- <script>
1136
- // Expose parent's run/runAgent/getIdToken in iframe for playground convenience
1137
- if (parent.run) window.run = parent.run.bind(parent);
1138
- if (parent.runAgent) window.runAgent = parent.runAgent.bind(parent);
1139
- if (parent.getIdToken) window.getIdToken = parent.getIdToken.bind(parent);
1140
-
1141
- globalThis.__tjs = {
1142
- version: '0.0.0',
1143
- pushStack: () => {},
1144
- popStack: () => {},
1145
- getStack: () => [],
1146
- typeError: (path, expected, value) => {
1147
- const actual = value === null ? 'null' : typeof value;
1148
- const err = new Error(\`Expected \${expected} for '\${path}', got \${actual}\`);
1149
- err.name = 'MonadicError';
1150
- err.path = path;
1151
- err.expected = expected;
1152
- err.actual = actual;
1153
- return err;
1154
- },
1155
- createRuntime: function() { return this; },
1156
- Is: (a, b) => {
1157
- if (a === b) return true;
1158
- if (a === null || b === null) return a === b;
1159
- if (typeof a !== typeof b) return false;
1160
- if (typeof a !== 'object') return false;
1161
- if (Array.isArray(a) && Array.isArray(b)) {
1162
- if (a.length !== b.length) return false;
1163
- return a.every((v, i) => globalThis.__tjs.Is(v, b[i]));
1164
- }
1165
- const keysA = Object.keys(a);
1166
- const keysB = Object.keys(b);
1167
- if (keysA.length !== keysB.length) return false;
1168
- return keysA.every(k => globalThis.__tjs.Is(a[k], b[k]));
1169
- },
1170
- IsNot: (a, b) => !globalThis.__tjs.Is(a, b),
1171
- };
1172
- </script>
1173
- <script type="module">
1174
- // Import statements must be at the top of the module
1175
- ${importStatements.join('\n ')}
1176
-
1177
- // Capture console.log
1178
- const _log = console.log;
1179
- console.log = (...args) => {
1180
- _log(...args);
1181
- parent.postMessage({ type: 'console', message: args.map(a => {
1182
- if (typeof a !== 'object' || a === null) return String(a);
1183
- try {
1184
- return JSON.stringify(a, null, 2);
1185
- } catch {
1186
- return String(a);
1187
- }
1188
- }).join(' ') }, '*');
1189
- };
1190
-
1191
- try {
1192
- // Instantiate WASM blocks (compiled in parent, instantiated here)
1193
- ${wasmInstantiationCode}
1194
-
1195
- const __execStart = performance.now();
1196
- ${codeWithoutImports}
1197
-
1198
- // Try to call the function if it exists and show result
1199
- const funcName = Object.keys(window).find(k => {
1200
- try { return typeof window[k] === 'function' && window[k].__tjs; }
1201
- catch { return false; }
1202
- });
1203
- if (funcName) {
1204
- const __callStart = performance.now();
1205
- const result = window[funcName]();
1206
- const __execTime = performance.now() - __callStart;
1207
- parent.postMessage({ type: 'timing', execTime: __execTime }, '*');
1208
- if (result !== undefined) {
1209
- // If result is a DOM node, append it; otherwise log it
1210
- if (result instanceof Node) {
1211
- document.body.append(result);
1212
- parent.postMessage({ type: 'hasPreviewContent' }, '*');
1213
- } else {
1214
- console.log('Result:', result);
1215
- }
1216
- }
1217
- } else {
1218
- // No TJS function found, report total parse/exec time
1219
- const __execTime = performance.now() - __execStart;
1220
- parent.postMessage({ type: 'timing', execTime: __execTime }, '*');
1221
- }
1222
- // Check if body has content after execution
1223
- if (document.body.children.length > 0) {
1224
- parent.postMessage({ type: 'hasPreviewContent' }, '*');
1225
- }
1226
- } catch (e) {
1227
- parent.postMessage({ type: 'error', message: e.message }, '*');
1228
- }
1229
- </script>
1230
- </body>
1231
- </html>`
1029
+ const iframeDoc = buildIframeDoc({
1030
+ cssContent,
1031
+ htmlContent,
1032
+ importMapScript,
1033
+ jsCode: codeWithoutImports,
1034
+ importStatements,
1035
+ parentBindings: true,
1036
+ autoCallTjsFunction: true,
1037
+ })
1232
1038
 
1233
1039
  // Listen for messages from iframe
1234
- const messageHandler = (event: MessageEvent) => {
1235
- if (event.data?.type === 'console') {
1236
- this.log(event.data.message)
1237
- } else if (event.data?.type === 'timing') {
1238
- // Update console header with execution time
1239
- const execTime = event.data.execTime
1240
- const execStr =
1241
- execTime < 1
1242
- ? `${(execTime * 1000).toFixed(0)}μs`
1243
- : `${execTime.toFixed(2)}ms`
1244
- this.parts.consoleHeader.textContent = `Console — executed in ${execStr}`
1245
- } else if (event.data?.type === 'hasPreviewContent') {
1246
- // Switch to Preview tab when content is added
1040
+ const messageHandler = createIframeMessageHandler({
1041
+ onConsole: (message) => this.log(message),
1042
+ onTiming: (execTime) => {
1043
+ this.parts.consoleHeader.textContent = `Console executed in ${formatExecTime(
1044
+ execTime
1045
+ )}`
1046
+ },
1047
+ onPreviewContent: () => {
1247
1048
  this.parts.outputTabs.value = 1 // Preview is second tab (index 1)
1248
- } else if (event.data?.type === 'error') {
1249
- this.log(`Error: ${event.data.message}`)
1049
+ },
1050
+ onError: (message) => {
1051
+ this.log(`Error: ${message}`)
1250
1052
  this.parts.statusBar.textContent = 'Runtime error'
1251
1053
  this.parts.statusBar.classList.add('error')
1252
- }
1253
- }
1054
+ },
1055
+ })
1254
1056
  window.addEventListener('message', messageHandler)
1255
1057
 
1256
1058
  // Set iframe content using blob URL instead of srcdoc
@@ -1348,16 +1150,9 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
1348
1150
  export const tjsPlayground = TJSPlayground.elementCreator({
1349
1151
  tag: 'tjs-playground',
1350
1152
  styleSpec: {
1351
- ':host': {
1352
- display: 'flex',
1353
- flexDirection: 'column',
1354
- height: '100%',
1355
- flex: '1 1 auto',
1356
- background: 'var(--background, #fff)',
1357
- color: 'var(--text-color, #1f2937)',
1358
- fontFamily: 'system-ui, sans-serif',
1359
- },
1153
+ ...sharedPlaygroundStyles,
1360
1154
 
1155
+ // TJS-specific: toolbar
1361
1156
  ':host .tjs-toolbar': {
1362
1157
  display: 'flex',
1363
1158
  alignItems: 'center',
@@ -1367,61 +1162,7 @@ export const tjsPlayground = TJSPlayground.elementCreator({
1367
1162
  borderBottom: '1px solid var(--code-border, #e5e7eb)',
1368
1163
  },
1369
1164
 
1370
- ':host .run-btn': {
1371
- display: 'flex',
1372
- alignItems: 'center',
1373
- gap: '4px',
1374
- padding: '6px 12px',
1375
- background: 'var(--brand-color, #3d4a6b)',
1376
- color: 'var(--brand-text-color, white)',
1377
- border: 'none',
1378
- borderRadius: '6px',
1379
- cursor: 'pointer',
1380
- fontWeight: '500',
1381
- fontSize: '14px',
1382
- },
1383
-
1384
- ':host .run-btn:hover:not(:disabled)': {
1385
- filter: 'brightness(1.1)',
1386
- },
1387
-
1388
- ':host .run-btn:disabled': {
1389
- opacity: '0.6',
1390
- cursor: 'not-allowed',
1391
- },
1392
-
1393
- ':host .toolbar-separator': {
1394
- width: '1px',
1395
- height: '20px',
1396
- background: 'var(--code-border, #d1d5db)',
1397
- },
1398
-
1399
- ':host .build-flags': {
1400
- display: 'flex',
1401
- alignItems: 'center',
1402
- gap: '12px',
1403
- },
1404
-
1405
- ':host .flag-label': {
1406
- display: 'flex',
1407
- alignItems: 'center',
1408
- gap: '4px',
1409
- fontSize: '13px',
1410
- color: 'var(--text-color, #6b7280)',
1411
- cursor: 'pointer',
1412
- userSelect: 'none',
1413
- },
1414
-
1415
- ':host .flag-label:hover': {
1416
- color: 'var(--text-color, #374151)',
1417
- },
1418
-
1419
- ':host .flag-label input[type="checkbox"]': {
1420
- margin: '0',
1421
- cursor: 'pointer',
1422
- accentColor: 'var(--brand-color, #3d4a6b)',
1423
- },
1424
-
1165
+ // TJS-specific: module name input
1425
1166
  ':host .module-name-input': {
1426
1167
  padding: '6px 10px',
1427
1168
  border: '1px solid var(--code-border, #d1d5db)',
@@ -1444,6 +1185,7 @@ export const tjsPlayground = TJSPlayground.elementCreator({
1444
1185
  opacity: '0.6',
1445
1186
  },
1446
1187
 
1188
+ // TJS-specific: save button
1447
1189
  ':host .save-btn': {
1448
1190
  display: 'flex',
1449
1191
  alignItems: 'center',
@@ -1464,46 +1206,7 @@ export const tjsPlayground = TJSPlayground.elementCreator({
1464
1206
  borderColor: 'var(--brand-color, #3d4a6b)',
1465
1207
  },
1466
1208
 
1467
- ':host .revert-btn': {
1468
- display: 'flex',
1469
- alignItems: 'center',
1470
- gap: '4px',
1471
- padding: '6px 12px',
1472
- background: 'var(--code-background, #e5e7eb)',
1473
- color: 'var(--text-color, #374151)',
1474
- border: '1px solid var(--code-border, #d1d5db)',
1475
- borderRadius: '6px',
1476
- cursor: 'pointer',
1477
- fontWeight: '500',
1478
- fontSize: '14px',
1479
- transition: 'opacity 0.2s',
1480
- },
1481
-
1482
- ':host .revert-btn:hover:not(:disabled)': {
1483
- background: '#fef3c7',
1484
- borderColor: '#f59e0b',
1485
- color: '#92400e',
1486
- },
1487
-
1488
- ':host .revert-btn:disabled': {
1489
- cursor: 'default',
1490
- },
1491
-
1492
- ':host .elastic': {
1493
- flex: '1',
1494
- },
1495
-
1496
- ':host .status-bar': {
1497
- fontSize: '13px',
1498
- color: 'var(--text-color, #6b7280)',
1499
- opacity: '0.7',
1500
- },
1501
-
1502
- ':host .status-bar.error': {
1503
- color: '#dc2626',
1504
- opacity: '1',
1505
- },
1506
-
1209
+ // TJS-specific: layout
1507
1210
  ':host .tjs-main': {
1508
1211
  display: 'flex',
1509
1212
  flex: '1 1 auto',
@@ -1521,31 +1224,7 @@ export const tjsPlayground = TJSPlayground.elementCreator({
1521
1224
  overflow: 'hidden',
1522
1225
  },
1523
1226
 
1524
- // Tab content panels need explicit background for dark mode
1525
- ':host tosi-tabs > [name]': {
1526
- background: 'var(--background, #fff)',
1527
- color: 'var(--text-color, #1f2937)',
1528
- },
1529
-
1530
- // Editor wrapper - contains the shadow DOM code-mirror component
1531
- ':host .editor-wrapper': {
1532
- flex: '1 1 auto',
1533
- height: '100%',
1534
- minHeight: '300px',
1535
- position: 'relative',
1536
- overflow: 'hidden',
1537
- },
1538
-
1539
- // code-mirror is shadow DOM, so we just size it - internal styles are handled by the component
1540
- ':host .editor-wrapper code-mirror': {
1541
- display: 'block',
1542
- position: 'absolute',
1543
- top: '0',
1544
- left: '0',
1545
- right: '0',
1546
- bottom: '0',
1547
- },
1548
-
1227
+ // TJS-specific: JS output panel
1549
1228
  ':host .js-output': {
1550
1229
  margin: '0',
1551
1230
  padding: '12px',
@@ -1558,176 +1237,12 @@ export const tjsPlayground = TJSPlayground.elementCreator({
1558
1237
  whiteSpace: 'pre-wrap',
1559
1238
  },
1560
1239
 
1561
- ':host .preview-frame': {
1562
- width: '100%',
1563
- height: '100%',
1564
- border: 'none',
1565
- background: 'var(--background, #fff)',
1566
- },
1567
-
1568
- ':host .docs-output': {
1569
- display: 'block',
1570
- padding: '12px 16px',
1571
- fontSize: '14px',
1572
- fontFamily: 'system-ui, sans-serif',
1573
- color: 'var(--text-color, inherit)',
1574
- background: 'var(--background, #fff)',
1575
- height: '100%',
1576
- overflow: 'auto',
1577
- },
1578
-
1579
- ':host .docs-output h2': {
1580
- fontSize: '1.25em',
1581
- marginTop: '0',
1582
- marginBottom: '0.5em',
1583
- color: 'var(--text-color, #1f2937)',
1584
- },
1585
-
1586
- ':host .docs-output pre': {
1587
- background: 'var(--code-background, #f3f4f6)',
1588
- padding: '8px 12px',
1589
- borderRadius: '6px',
1590
- overflow: 'auto',
1591
- fontSize: '13px',
1592
- },
1593
-
1594
- ':host .docs-output code': {
1595
- fontFamily: 'ui-monospace, monospace',
1596
- fontSize: '0.9em',
1597
- },
1598
-
1599
- ':host .docs-output p': {
1600
- margin: '0.75em 0',
1601
- lineHeight: '1.5',
1602
- },
1603
-
1604
- ':host .docs-output h3': {
1605
- fontSize: '1em',
1606
- marginTop: '1em',
1607
- marginBottom: '0.5em',
1608
- },
1609
-
1610
- ':host .docs-output ul': {
1611
- paddingLeft: '1.5em',
1612
- margin: '0.5em 0',
1613
- },
1614
-
1615
- ':host .docs-output li': {
1616
- marginBottom: '0.25em',
1617
- },
1618
-
1619
- ':host .docs-output hr': {
1620
- border: 'none',
1621
- borderTop: '1px solid var(--code-border, #e5e7eb)',
1622
- margin: '1.5em 0',
1623
- },
1624
-
1625
- ':host .tests-output': {
1626
- padding: '12px',
1627
- fontSize: '14px',
1628
- fontFamily: 'system-ui, sans-serif',
1629
- color: 'var(--text-color, inherit)',
1630
- background: 'var(--background, #fff)',
1631
- height: '100%',
1632
- overflow: 'auto',
1633
- },
1634
-
1635
- ':host .test-summary': {
1636
- marginBottom: '12px',
1637
- paddingBottom: '8px',
1638
- borderBottom: '1px solid var(--code-border, #e5e7eb)',
1639
- },
1640
-
1641
- ':host .test-failed': {
1642
- color: '#dc2626',
1643
- },
1644
-
1645
- ':host .test-list': {
1646
- listStyle: 'none',
1647
- padding: 0,
1648
- margin: 0,
1649
- },
1650
-
1651
- ':host .test-list li': {
1652
- padding: '4px 0',
1653
- },
1654
-
1655
- ':host .test-pass': {
1656
- color: '#16a34a',
1657
- },
1658
-
1659
- ':host .test-fail': {
1660
- color: '#dc2626',
1661
- },
1662
-
1663
- ':host .test-error': {
1664
- marginLeft: '20px',
1665
- marginTop: '4px',
1666
- padding: '8px',
1667
- background: 'rgba(220, 38, 38, 0.1)',
1668
- borderRadius: '4px',
1669
- fontSize: '13px',
1670
- fontFamily: 'var(--font-mono, monospace)',
1671
- },
1672
-
1673
- ':host .clickable-error': {
1674
- cursor: 'pointer',
1675
- textDecoration: 'underline',
1676
- textDecorationStyle: 'dotted',
1677
- },
1678
-
1679
- ':host .clickable-error:hover': {
1680
- background: 'rgba(220, 38, 38, 0.2)',
1681
- },
1682
-
1683
- ':host .sig-badge': {
1684
- fontSize: '11px',
1685
- padding: '2px 6px',
1686
- marginLeft: '8px',
1687
- background: 'rgba(99, 102, 241, 0.1)',
1688
- color: '#6366f1',
1689
- borderRadius: '4px',
1690
- },
1691
-
1240
+ // TJS-specific: console container class name
1692
1241
  ':host .tjs-console': {
1693
1242
  height: '120px',
1694
1243
  borderTop: '1px solid var(--code-border, #e5e7eb)',
1695
1244
  display: 'flex',
1696
1245
  flexDirection: 'column',
1697
1246
  },
1698
-
1699
- ':host .console-header': {
1700
- padding: '4px 12px',
1701
- background: 'var(--code-background, #f3f4f6)',
1702
- fontSize: '12px',
1703
- fontWeight: '500',
1704
- color: 'var(--text-color, #6b7280)',
1705
- opacity: '0.7',
1706
- borderBottom: '1px solid var(--code-border, #e5e7eb)',
1707
- },
1708
-
1709
- ':host .console-output': {
1710
- flex: '1',
1711
- margin: '0',
1712
- padding: '8px 12px',
1713
- background: 'var(--code-background, #f3f4f6)',
1714
- color: 'var(--text-color, #1f2937)',
1715
- fontSize: '12px',
1716
- fontFamily: 'ui-monospace, monospace',
1717
- overflow: 'auto',
1718
- whiteSpace: 'pre-wrap',
1719
- },
1720
-
1721
- ':host .clickable-line': {
1722
- cursor: 'pointer',
1723
- color: '#2563eb',
1724
- textDecoration: 'underline',
1725
- textDecorationStyle: 'dotted',
1726
- },
1727
-
1728
- ':host .clickable-line:hover': {
1729
- color: '#1d4ed8',
1730
- background: 'rgba(37, 99, 235, 0.1)',
1731
- },
1732
1247
  },
1733
1248
  }) as ElementCreator<TJSPlayground>